diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 58c181022a8..99ea7b3223d 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -27,7 +27,7 @@ jobs: - name: Run cargo-tarpaulin uses: actions-rs/tarpaulin@v0.1 with: - args: --features intl --ignore-tests + args: --features intl --ignore-tests --engine llvm - name: Upload to codecov.io uses: codecov/codecov-action@v3 diff --git a/Cargo.lock b/Cargo.lock index 423428b13ef..599fe559ebb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,21 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "addr2line" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + [[package]] name = "aho-corasick" version = "0.7.19" @@ -26,12 +41,6 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" -[[package]] -name = "anyhow" -version = "1.0.66" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "216261ddc8289130e551ddcd5ce8a064710c0d064a4d2895c67151c92b5443f6" - [[package]] name = "arbitrary" version = "1.2.0" @@ -58,6 +67,21 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "backtrace" +version = "0.3.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab84319d616cfb654d03394f38ab7e6f0919e181b1b57e1fd15e7fb4077d9a7" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -83,9 +107,8 @@ version = "0.16.0" dependencies = [ "boa_ast", "boa_engine", - "boa_interner", "boa_parser", - "clap 4.0.22", + "clap 4.0.26", "colored", "jemallocator", "phf", @@ -112,7 +135,6 @@ dependencies = [ "dyn-clone", "fast-float", "float-cmp", - "gc", "icu_datetime", "icu_locale_canonicalizer", "icu_locid", @@ -148,15 +170,14 @@ dependencies = [ "boa_gc", "boa_interner", "boa_parser", - "gc", ] [[package]] name = "boa_gc" version = "0.16.0" dependencies = [ - "gc", - "measureme", + "boa_macros", + "boa_profiler", ] [[package]] @@ -177,8 +198,10 @@ dependencies = [ name = "boa_macros" version = "0.16.0" dependencies = [ + "proc-macro2", "quote", "syn", + "synstructure", ] [[package]] @@ -209,22 +232,21 @@ dependencies = [ name = "boa_tester" version = "0.16.0" dependencies = [ - "anyhow", "bitflags", "boa_engine", "boa_gc", - "boa_interner", "boa_parser", - "clap 4.0.22", + "clap 4.0.26", + "color-eyre", "colored", "fxhash", - "gc", "once_cell", "rayon", "regex", "serde", "serde_json", "serde_yaml", + "toml", ] [[package]] @@ -245,9 +267,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.11.0" +version = "3.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1ad822118d20d2c234f427000d5acc36eabe1e29a348c89b63dd60b13f28e5d" +checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" [[package]] name = "byteorder" @@ -263,9 +285,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.0.73" +version = "1.0.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" +checksum = "581f5dba903aac52ea3feb5ec4810848460ee833876f1f9b0fdeab1f19091574" [[package]] name = "cfg-if" @@ -275,9 +297,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.22" +version = "0.4.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfd4d1b31faaa3a89d7934dbded3111da0d2ef28e3ebccdb4f0179f5929d1ef1" +checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f" dependencies = [ "iana-time-zone", "js-sys", @@ -317,9 +339,9 @@ dependencies = [ [[package]] name = "clap" -version = "3.2.22" +version = "3.2.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86447ad904c7fb335a790c9d7fe3d0d971dc523b8ccd1561a520de9a85302750" +checksum = "71655c45cb9845d3270c9d6df84ebe72b4dad3c2ba3f7023ad47c144e4e473a5" dependencies = [ "bitflags", "clap_lex 0.2.4", @@ -329,9 +351,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.0.22" +version = "4.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91b9970d7505127a162fdaa9b96428d28a479ba78c9ec7550a63a5d9863db682" +checksum = "2148adefda54e14492fb9bddcc600b4344c5d1a3123bd666dcb939c6f0e0e57e" dependencies = [ "atty", "bitflags", @@ -394,6 +416,33 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "color-eyre" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a667583cca8c4f8436db8de46ea8233c42a7d9ae424a82d338f2e4675229204" +dependencies = [ + "backtrace", + "color-spantrace", + "eyre", + "indenter", + "once_cell", + "owo-colors", + "tracing-error", +] + +[[package]] +name = "color-spantrace" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ba75b3d9449ecdccb27ecbc479fdc0b87fa2dd43d2f8298f9bf0e59aacc8dce" +dependencies = [ + "once_cell", + "owo-colors", + "tracing-core", + "tracing-error", +] + [[package]] name = "colored" version = "2.0.0" @@ -421,7 +470,7 @@ dependencies = [ "atty", "cast", "ciborium", - "clap 3.2.22", + "clap 3.2.23", "criterion-plot", "itertools", "lazy_static", @@ -492,9 +541,9 @@ dependencies = [ [[package]] name = "cxx" -version = "1.0.78" +version = "1.0.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19f39818dcfc97d45b03953c1292efc4e80954e1583c4aa770bac1383e2310a4" +checksum = "6b7d4e43b25d3c994662706a1d4fcfc32aaa6afd287502c111b237093bb23f3a" dependencies = [ "cc", "cxxbridge-flags", @@ -504,9 +553,9 @@ dependencies = [ [[package]] name = "cxx-build" -version = "1.0.78" +version = "1.0.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e580d70777c116df50c390d1211993f62d40302881e54d4b79727acb83d0199" +checksum = "84f8829ddc213e2c1368e51a2564c552b65a8cb6a28f31e576270ac81d5e5827" dependencies = [ "cc", "codespan-reporting", @@ -519,15 +568,15 @@ dependencies = [ [[package]] name = "cxxbridge-flags" -version = "1.0.78" +version = "1.0.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56a46460b88d1cec95112c8c363f0e2c39afdb237f60583b0b36343bf627ea9c" +checksum = "e72537424b474af1460806647c41d4b6d35d09ef7fe031c5c2fa5766047cc56a" [[package]] name = "cxxbridge-macro" -version = "1.0.78" +version = "1.0.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "747b608fecf06b0d72d440f27acc99288207324b793be2c17991839f3d4995ea" +checksum = "309e4fb93eed90e1e14bea0da16b209f81813ba9fc7830c20ed151dd7bc0a4d7" dependencies = [ "proc-macro2", "quote", @@ -626,6 +675,16 @@ dependencies = [ "str-buf", ] +[[package]] +name = "eyre" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c2b6b5a29c02cdc822728b7d7b8ae1bab3e3b05d44522770ddd49722eeac7eb" +dependencies = [ + "indenter", + "once_cell", +] + [[package]] name = "fast-float" version = "0.2.0" @@ -634,9 +693,9 @@ checksum = "95765f67b4b18863968b4a1bd5bb576f732b29a4a28c7cd84c09fa3e2875f33c" [[package]] name = "fd-lock" -version = "3.0.6" +version = "3.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e11dcc7e4d79a8c89b9ab4c6f5c30b1fc4a83c420792da3542fd31179ed5f517" +checksum = "0c93a581058d957dc4176875aad04f82f81613e6611d64aa1a9c755bdfb16711" dependencies = [ "cfg-if", "rustix", @@ -679,27 +738,6 @@ dependencies = [ "byteorder", ] -[[package]] -name = "gc" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3edaac0f5832202ebc99520cb77c932248010c4645d20be1dc62d6579f5b3752" -dependencies = [ - "gc_derive", -] - -[[package]] -name = "gc_derive" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60df8444f094ff7885631d80e78eb7d88c3c2361a98daaabb06256e4500db941" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "synstructure", -] - [[package]] name = "getrandom" version = "0.2.8" @@ -713,6 +751,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "gimli" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d" + [[package]] name = "half" version = "1.8.2" @@ -742,9 +786,9 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.51" +version = "0.1.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5a6ef98976b22b3b7f2f3a806f858cb862044cfa66805aa3ad84cb3d3b785ed" +checksum = "64c122667b287044802d6ce17ee2ddf13207ed924c712de9a66a5814d5b64765" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -756,9 +800,9 @@ dependencies = [ [[package]] name = "iana-time-zone-haiku" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fde6edd6cef363e9359ed3c98ba64590ba9eecba2293eb5a723ab32aee8926aa" +checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca" dependencies = [ "cxx", "cxx-build", @@ -893,11 +937,17 @@ dependencies = [ "icu_provider_blob", ] +[[package]] +name = "indenter" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" + [[package]] name = "indexmap" -version = "1.9.1" +version = "1.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" +checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" dependencies = [ "autocfg", "hashbrown", @@ -914,9 +964,9 @@ dependencies = [ [[package]] name = "io-lifetimes" -version = "0.7.3" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ea37f355c05dde75b84bba2d767906ad522e97cd9e2eef2be7a4ab7fb442c06" +checksum = "59ce5ef949d49ee85593fc4d3f3f95ad61657076395cbbce23e2121fc5542074" [[package]] name = "itertools" @@ -971,9 +1021,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.135" +version = "0.2.137" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68783febc7782c6c5cb401fbda4de5a9898be1762314da0bb2c10ced61f18b0c" +checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89" [[package]] name = "link-cplusplus" @@ -1057,6 +1107,15 @@ dependencies = [ "autocfg", ] +[[package]] +name = "miniz_oxide" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96590ba8f175222643a85693f33d26e9c8a015f599c216509b1a6894af675d34" +dependencies = [ + "adler", +] + [[package]] name = "nibble_vec" version = "0.1.0" @@ -1111,14 +1170,23 @@ dependencies = [ [[package]] name = "num_cpus" -version = "1.13.1" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" +checksum = "f6058e64324c71e02bc2b150e4f3bc8286db6c83092132ffa3f6b1eab0f9def5" dependencies = [ "hermit-abi", "libc", ] +[[package]] +name = "object" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21158b2c33aa6d4561f1c0a6ea283ca92bc54802a93b263e910746d679a7eb53" +dependencies = [ + "memchr", +] + [[package]] name = "once_cell" version = "1.16.0" @@ -1133,9 +1201,15 @@ checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" [[package]] name = "os_str_bytes" -version = "6.3.0" +version = "6.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ff7415e9ae3fff1225851df9e0d9e4e5479f947619774677a63572e55e80eff" +checksum = "3baf96e39c5359d2eb0dd6ccb42c62b91d9678aa68160d261b9e0ccbf9e9dea9" + +[[package]] +name = "owo-colors" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" [[package]] name = "parking_lot" @@ -1213,6 +1287,12 @@ dependencies = [ "siphasher", ] +[[package]] +name = "pin-project-lite" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" + [[package]] name = "plotters" version = "0.3.4" @@ -1259,9 +1339,9 @@ checksum = "7c68cb38ed13fd7bc9dd5db8f165b7c8d9c1a315104083a2b10f11354c2af97f" [[package]] name = "ppv-lite86" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro-error" @@ -1289,9 +1369,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.46" +version = "1.0.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94e2ef8dbfc347b10c094890f778ee2e36ca9bb4262e86dc99cd217e35f3470b" +checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" dependencies = [ "unicode-ident", ] @@ -1347,11 +1427,10 @@ dependencies = [ [[package]] name = "rayon" -version = "1.5.3" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd99e5772ead8baa5215278c9b15bf92087709e9c1b2d1f97cdb5a183c933a7d" +checksum = "1e060280438193c554f654141c9ea9417886713b7acd75974c85b18a69a88e0b" dependencies = [ - "autocfg", "crossbeam-deque", "either", "rayon-core", @@ -1359,9 +1438,9 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.9.3" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "258bcdb5ac6dad48491bb2992db6b7cf74878b0384908af124823d118c99683f" +checksum = "cac410af5d00ab6884528b4ab69d1e8e146e8d471201800fa1b4524126de6ad3" dependencies = [ "crossbeam-channel", "crossbeam-deque", @@ -1402,9 +1481,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.27" +version = "0.6.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" +checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" [[package]] name = "regress" @@ -1415,6 +1494,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "rustc-demangle" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" + [[package]] name = "rustc-hash" version = "1.1.0" @@ -1423,9 +1508,9 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustix" -version = "0.35.11" +version = "0.35.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbb2fda4666def1433b1b05431ab402e42a1084285477222b72d6c564c417cef" +checksum = "727a1a6d65f786ec22df8a81ca3121107f235970dc1705ed681d3e6e8b9cd5f9" dependencies = [ "bitflags", "errno", @@ -1524,9 +1609,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.87" +version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce777b7b150d76b9cf60d28b55f5847135a003f7d7350c6be7a773508ce7d45" +checksum = "020ff22c755c2ed3f8cf162dbb41a7268d934702f3ed3631656ea597e08fc3db" dependencies = [ "itoa", "ryu", @@ -1546,6 +1631,15 @@ dependencies = [ "unsafe-libyaml", ] +[[package]] +name = "sharded-slab" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" +dependencies = [ + "lazy_static", +] + [[package]] name = "siphasher" version = "0.3.10" @@ -1644,9 +1738,9 @@ dependencies = [ [[package]] name = "textwrap" -version = "0.15.1" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "949517c0cf1bf4ee812e2e07e08ab448e3ae0d23472aee8a06c985f0c8815b16" +checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" [[package]] name = "thiserror" @@ -1668,6 +1762,15 @@ dependencies = [ "syn", ] +[[package]] +name = "thread_local" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" +dependencies = [ + "once_cell", +] + [[package]] name = "time" version = "0.1.44" @@ -1716,6 +1819,57 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" +[[package]] +name = "toml" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" +dependencies = [ + "serde", +] + +[[package]] +name = "tracing" +version = "0.1.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +dependencies = [ + "cfg-if", + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-error" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d686ec1c0f384b1277f097b2f279a2ecc11afe8c133c1aabf036a27cb4cd206e" +dependencies = [ + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6176eae26dd70d0c919749377897b54a9276bd7061339665dd68777926b5a70" +dependencies = [ + "sharded-slab", + "thread_local", + "tracing-core", +] + [[package]] name = "unicode-general-category" version = "0.6.0" @@ -1767,6 +1921,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "936e4b492acfd135421d8dca4b1aa80a7bfc26e702ef3af710e0752684df5372" +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + [[package]] name = "version_check" version = "0.9.4" @@ -1893,46 +2053,60 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-sys" -version = "0.36.1" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" dependencies = [ + "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", "windows_i686_msvc", "windows_x86_64_gnu", + "windows_x86_64_gnullvm", "windows_x86_64_msvc", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" + [[package]] name = "windows_aarch64_msvc" -version = "0.36.1" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" +checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" [[package]] name = "windows_i686_gnu" -version = "0.36.1" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" +checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" [[package]] name = "windows_i686_msvc" -version = "0.36.1" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" +checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" [[package]] name = "windows_x86_64_gnu" -version = "0.36.1" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" +checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" [[package]] name = "windows_x86_64_msvc" -version = "0.36.1" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" +checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" [[package]] name = "writeable" @@ -1975,9 +2149,9 @@ dependencies = [ [[package]] name = "zerofrom-derive" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8785f47d6062c1932866147f91297286a9f350b3070e9d9f0b6078e37d623c1a" +checksum = "2e8aa86add9ddbd2409c1ed01e033cd457d79b1b1229b64922c25095c595e829" dependencies = [ "proc-macro2", "quote", diff --git a/boa_ast/src/declaration/mod.rs b/boa_ast/src/declaration/mod.rs index 02f3e584c85..fa89cca1f58 100644 --- a/boa_ast/src/declaration/mod.rs +++ b/boa_ast/src/declaration/mod.rs @@ -52,12 +52,12 @@ pub enum Declaration { impl ToIndentedString for Declaration { fn to_indented_string(&self, interner: &Interner, indentation: usize) -> String { match self { - Declaration::Function(f) => f.to_indented_string(interner, indentation), - Declaration::Generator(g) => g.to_indented_string(interner, indentation), - Declaration::AsyncFunction(af) => af.to_indented_string(interner, indentation), - Declaration::AsyncGenerator(ag) => ag.to_indented_string(interner, indentation), - Declaration::Class(c) => c.to_indented_string(interner, indentation), - Declaration::Lexical(l) => { + Self::Function(f) => f.to_indented_string(interner, indentation), + Self::Generator(g) => g.to_indented_string(interner, indentation), + Self::AsyncFunction(af) => af.to_indented_string(interner, indentation), + Self::AsyncGenerator(ag) => ag.to_indented_string(interner, indentation), + Self::Class(c) => c.to_indented_string(interner, indentation), + Self::Lexical(l) => { let mut s = l.to_interned_string(interner); s.push(';'); s @@ -72,12 +72,12 @@ impl VisitWith for Declaration { V: Visitor<'a>, { match self { - Declaration::Function(f) => visitor.visit_function(f), - Declaration::Generator(g) => visitor.visit_generator(g), - Declaration::AsyncFunction(af) => visitor.visit_async_function(af), - Declaration::AsyncGenerator(ag) => visitor.visit_async_generator(ag), - Declaration::Class(c) => visitor.visit_class(c), - Declaration::Lexical(ld) => visitor.visit_lexical_declaration(ld), + Self::Function(f) => visitor.visit_function(f), + Self::Generator(g) => visitor.visit_generator(g), + Self::AsyncFunction(af) => visitor.visit_async_function(af), + Self::AsyncGenerator(ag) => visitor.visit_async_generator(ag), + Self::Class(c) => visitor.visit_class(c), + Self::Lexical(ld) => visitor.visit_lexical_declaration(ld), } } @@ -86,12 +86,12 @@ impl VisitWith for Declaration { V: VisitorMut<'a>, { match self { - Declaration::Function(f) => visitor.visit_function_mut(f), - Declaration::Generator(g) => visitor.visit_generator_mut(g), - Declaration::AsyncFunction(af) => visitor.visit_async_function_mut(af), - Declaration::AsyncGenerator(ag) => visitor.visit_async_generator_mut(ag), - Declaration::Class(c) => visitor.visit_class_mut(c), - Declaration::Lexical(ld) => visitor.visit_lexical_declaration_mut(ld), + Self::Function(f) => visitor.visit_function_mut(f), + Self::Generator(g) => visitor.visit_generator_mut(g), + Self::AsyncFunction(af) => visitor.visit_async_function_mut(af), + Self::AsyncGenerator(ag) => visitor.visit_async_generator_mut(ag), + Self::Class(c) => visitor.visit_class_mut(c), + Self::Lexical(ld) => visitor.visit_lexical_declaration_mut(ld), } } } diff --git a/boa_ast/src/declaration/variable.rs b/boa_ast/src/declaration/variable.rs index 921f57ffe1e..e3012a7e270 100644 --- a/boa_ast/src/declaration/variable.rs +++ b/boa_ast/src/declaration/variable.rs @@ -51,7 +51,7 @@ pub struct VarDeclaration(pub VariableList); impl From for Statement { fn from(var: VarDeclaration) -> Self { - Statement::Var(var) + Self::Var(var) } } @@ -110,16 +110,16 @@ pub enum LexicalDeclaration { impl LexicalDeclaration { /// Gets the inner variable list of the `LexicalDeclaration` #[must_use] - pub fn variable_list(&self) -> &VariableList { + pub const fn variable_list(&self) -> &VariableList { match self { - LexicalDeclaration::Const(list) | LexicalDeclaration::Let(list) => list, + Self::Const(list) | Self::Let(list) => list, } } } impl From for Declaration { fn from(lex: LexicalDeclaration) -> Self { - Declaration::Lexical(lex) + Self::Lexical(lex) } } @@ -128,8 +128,8 @@ impl ToInternedString for LexicalDeclaration { format!( "{} {}", match &self { - LexicalDeclaration::Let(_) => "let", - LexicalDeclaration::Const(_) => "const", + Self::Let(_) => "let", + Self::Const(_) => "const", }, self.variable_list().to_interned_string(interner) ) @@ -142,9 +142,7 @@ impl VisitWith for LexicalDeclaration { V: Visitor<'a>, { match self { - LexicalDeclaration::Const(vars) | LexicalDeclaration::Let(vars) => { - visitor.visit_variable_list(vars) - } + Self::Const(vars) | Self::Let(vars) => visitor.visit_variable_list(vars), } } @@ -153,9 +151,7 @@ impl VisitWith for LexicalDeclaration { V: VisitorMut<'a>, { match self { - LexicalDeclaration::Const(vars) | LexicalDeclaration::Let(vars) => { - visitor.visit_variable_list_mut(vars) - } + Self::Const(vars) | Self::Let(vars) => visitor.visit_variable_list_mut(vars), } } } @@ -176,7 +172,7 @@ impl VariableList { return None; } - Some(VariableList { list }) + Some(Self { list }) } } @@ -228,7 +224,7 @@ impl TryFrom> for VariableList { type Error = TryFromVariableListError; fn try_from(value: Box<[Variable]>) -> Result { - VariableList::new(value).ok_or(TryFromVariableListError(())) + Self::new(value).ok_or(TryFromVariableListError(())) } } @@ -236,7 +232,7 @@ impl TryFrom> for VariableList { type Error = TryFromVariableListError; fn try_from(value: Vec) -> Result { - VariableList::try_from(value.into_boxed_slice()) + Self::try_from(value.into_boxed_slice()) } } @@ -275,7 +271,7 @@ impl Variable { /// Creates a new variable declaration from a `BindingIdentifier`. #[inline] #[must_use] - pub fn from_identifier(ident: Identifier, init: Option) -> Self { + pub const fn from_identifier(ident: Identifier, init: Option) -> Self { Self { binding: Binding::Identifier(ident), init, @@ -285,7 +281,7 @@ impl Variable { /// Creates a new variable declaration from a `Pattern`. #[inline] #[must_use] - pub fn from_pattern(pattern: Pattern, init: Option) -> Self { + pub const fn from_pattern(pattern: Pattern, init: Option) -> Self { Self { binding: Binding::Pattern(pattern), init, @@ -293,14 +289,14 @@ impl Variable { } /// Gets the variable declaration binding. #[must_use] - pub fn binding(&self) -> &Binding { + pub const fn binding(&self) -> &Binding { &self.binding } /// Gets the initialization expression for the variable declaration, if any. #[inline] #[must_use] - pub fn init(&self) -> Option<&Expression> { + pub const fn init(&self) -> Option<&Expression> { self.init.as_ref() } } @@ -360,8 +356,8 @@ impl From for Binding { impl ToInternedString for Binding { fn to_interned_string(&self, interner: &Interner) -> String { match self { - Binding::Identifier(id) => id.to_interned_string(interner), - Binding::Pattern(ref pattern) => pattern.to_interned_string(interner), + Self::Identifier(id) => id.to_interned_string(interner), + Self::Pattern(ref pattern) => pattern.to_interned_string(interner), } } } @@ -372,8 +368,8 @@ impl VisitWith for Binding { V: Visitor<'a>, { match self { - Binding::Identifier(id) => visitor.visit_identifier(id), - Binding::Pattern(pattern) => visitor.visit_pattern(pattern), + Self::Identifier(id) => visitor.visit_identifier(id), + Self::Pattern(pattern) => visitor.visit_pattern(pattern), } } @@ -382,8 +378,8 @@ impl VisitWith for Binding { V: VisitorMut<'a>, { match self { - Binding::Identifier(id) => visitor.visit_identifier_mut(id), - Binding::Pattern(pattern) => visitor.visit_pattern_mut(pattern), + Self::Identifier(id) => visitor.visit_identifier_mut(id), + Self::Pattern(pattern) => visitor.visit_pattern_mut(pattern), } } } diff --git a/boa_ast/src/expression/access.rs b/boa_ast/src/expression/access.rs index b66f2a11cff..9a8abe348a4 100644 --- a/boa_ast/src/expression/access.rs +++ b/boa_ast/src/expression/access.rs @@ -53,8 +53,8 @@ impl VisitWith for PropertyAccessField { V: Visitor<'a>, { match self { - PropertyAccessField::Const(sym) => visitor.visit_sym(sym), - PropertyAccessField::Expr(expr) => visitor.visit_expression(expr), + Self::Const(sym) => visitor.visit_sym(sym), + Self::Expr(expr) => visitor.visit_expression(expr), } } @@ -63,8 +63,8 @@ impl VisitWith for PropertyAccessField { V: VisitorMut<'a>, { match self { - PropertyAccessField::Const(sym) => visitor.visit_sym_mut(sym), - PropertyAccessField::Expr(expr) => visitor.visit_expression_mut(&mut *expr), + Self::Const(sym) => visitor.visit_sym_mut(sym), + Self::Expr(expr) => visitor.visit_expression_mut(&mut *expr), } } } @@ -88,9 +88,9 @@ impl ToInternedString for PropertyAccess { #[inline] fn to_interned_string(&self, interner: &Interner) -> String { match self { - PropertyAccess::Simple(s) => s.to_interned_string(interner), - PropertyAccess::Private(p) => p.to_interned_string(interner), - PropertyAccess::Super(s) => s.to_interned_string(interner), + Self::Simple(s) => s.to_interned_string(interner), + Self::Private(p) => p.to_interned_string(interner), + Self::Super(s) => s.to_interned_string(interner), } } } @@ -108,9 +108,9 @@ impl VisitWith for PropertyAccess { V: Visitor<'a>, { match self { - PropertyAccess::Simple(spa) => visitor.visit_simple_property_access(spa), - PropertyAccess::Private(ppa) => visitor.visit_private_property_access(ppa), - PropertyAccess::Super(supa) => visitor.visit_super_property_access(supa), + Self::Simple(spa) => visitor.visit_simple_property_access(spa), + Self::Private(ppa) => visitor.visit_private_property_access(ppa), + Self::Super(supa) => visitor.visit_super_property_access(supa), } } @@ -119,9 +119,9 @@ impl VisitWith for PropertyAccess { V: VisitorMut<'a>, { match self { - PropertyAccess::Simple(spa) => visitor.visit_simple_property_access_mut(spa), - PropertyAccess::Private(ppa) => visitor.visit_private_property_access_mut(ppa), - PropertyAccess::Super(supa) => visitor.visit_super_property_access_mut(supa), + Self::Simple(spa) => visitor.visit_simple_property_access_mut(spa), + Self::Private(ppa) => visitor.visit_private_property_access_mut(ppa), + Self::Super(supa) => visitor.visit_super_property_access_mut(supa), } } } @@ -139,14 +139,14 @@ impl SimplePropertyAccess { /// Gets the target object of the property access. #[inline] #[must_use] - pub fn target(&self) -> &Expression { + pub const fn target(&self) -> &Expression { &self.target } /// Gets the accessed field of the target object. #[inline] #[must_use] - pub fn field(&self) -> &PropertyAccessField { + pub const fn field(&self) -> &PropertyAccessField { &self.field } @@ -233,14 +233,14 @@ impl PrivatePropertyAccess { /// Gets the original object from where to get the field from. #[inline] #[must_use] - pub fn target(&self) -> &Expression { + pub const fn target(&self) -> &Expression { &self.target } /// Gets the name of the field to retrieve. #[inline] #[must_use] - pub fn field(&self) -> Sym { + pub const fn field(&self) -> Sym { self.field } } @@ -298,14 +298,14 @@ pub struct SuperPropertyAccess { impl SuperPropertyAccess { /// Creates a new property access field node. #[must_use] - pub fn new(field: PropertyAccessField) -> Self { + pub const fn new(field: PropertyAccessField) -> Self { Self { field } } /// Gets the name of the field to retrieve. #[inline] #[must_use] - pub fn field(&self) -> &PropertyAccessField { + pub const fn field(&self) -> &PropertyAccessField { &self.field } } diff --git a/boa_ast/src/expression/await.rs b/boa_ast/src/expression/await.rs index 8c8786311ad..079bf831c31 100644 --- a/boa_ast/src/expression/await.rs +++ b/boa_ast/src/expression/await.rs @@ -26,7 +26,7 @@ impl Await { /// Return the target expression that should be awaited. #[inline] #[must_use] - pub fn target(&self) -> &Expression { + pub const fn target(&self) -> &Expression { &self.target } } diff --git a/boa_ast/src/expression/call.rs b/boa_ast/src/expression/call.rs index 3787ad99d84..5b978242615 100644 --- a/boa_ast/src/expression/call.rs +++ b/boa_ast/src/expression/call.rs @@ -42,14 +42,14 @@ impl Call { /// Gets the target function of this call expression. #[inline] #[must_use] - pub fn function(&self) -> &Expression { + pub const fn function(&self) -> &Expression { &self.function } /// Retrieves the arguments passed to the function. #[inline] #[must_use] - pub fn args(&self) -> &[Expression] { + pub const fn args(&self) -> &[Expression] { &self.args } } @@ -122,7 +122,7 @@ impl SuperCall { /// Retrieves the arguments of the super call. #[must_use] - pub fn arguments(&self) -> &[Expression] { + pub const fn arguments(&self) -> &[Expression] { &self.args } } diff --git a/boa_ast/src/expression/identifier.rs b/boa_ast/src/expression/identifier.rs index 0bf6844744f..0f7f73a05b7 100644 --- a/boa_ast/src/expression/identifier.rs +++ b/boa_ast/src/expression/identifier.rs @@ -68,14 +68,14 @@ impl Identifier { /// Creates a new identifier AST Expression. #[inline] #[must_use] - pub fn new(ident: Sym) -> Self { + pub const fn new(ident: Sym) -> Self { Self { ident } } /// Retrieves the identifier's string symbol in the interner. #[inline] #[must_use] - pub fn sym(self) -> Sym { + pub const fn sym(self) -> Sym { self.ident } } diff --git a/boa_ast/src/expression/literal/array.rs b/boa_ast/src/expression/literal/array.rs index 631d0c9fb3a..2f599d12cf8 100644 --- a/boa_ast/src/expression/literal/array.rs +++ b/boa_ast/src/expression/literal/array.rs @@ -47,7 +47,7 @@ impl ArrayLiteral { /// Indicates if a spread operator in the array literal has a trailing comma. /// This is a syntax error in some cases. #[must_use] - pub fn has_trailing_comma_spread(&self) -> bool { + pub const fn has_trailing_comma_spread(&self) -> bool { self.has_trailing_comma_spread } diff --git a/boa_ast/src/expression/literal/mod.rs b/boa_ast/src/expression/literal/mod.rs index 60951351d3b..beed8d8f900 100644 --- a/boa_ast/src/expression/literal/mod.rs +++ b/boa_ast/src/expression/literal/mod.rs @@ -167,7 +167,7 @@ impl From for Literal { impl From for Expression { #[inline] fn from(lit: Literal) -> Self { - Expression::Literal(lit) + Self::Literal(lit) } } @@ -193,7 +193,7 @@ impl VisitWith for Literal { where V: Visitor<'a>, { - if let Literal::String(sym) = self { + if let Self::String(sym) = self { visitor.visit_sym(sym) } else { ControlFlow::Continue(()) @@ -204,7 +204,7 @@ impl VisitWith for Literal { where V: VisitorMut<'a>, { - if let Literal::String(sym) = self { + if let Self::String(sym) = self { visitor.visit_sym_mut(sym) } else { ControlFlow::Continue(()) diff --git a/boa_ast/src/expression/literal/object.rs b/boa_ast/src/expression/literal/object.rs index 1736a2e31f2..59c7587754a 100644 --- a/boa_ast/src/expression/literal/object.rs +++ b/boa_ast/src/expression/literal/object.rs @@ -43,7 +43,7 @@ impl ObjectLiteral { /// Gets the object literal properties #[inline] #[must_use] - pub fn properties(&self) -> &[PropertyDefinition] { + pub const fn properties(&self) -> &[PropertyDefinition] { &self.properties } @@ -121,18 +121,12 @@ impl ObjectLiteral { return None; } excluded_keys.push(*ident); - bindings.push(ObjectPatternElement::SingleName { - ident: *ident, - name: PropertyName::Literal(name), - default_init: Some(assign.rhs().clone()), - }); - } else { - bindings.push(ObjectPatternElement::SingleName { - ident: *ident, - name: PropertyName::Literal(name), - default_init: Some(assign.rhs().clone()), - }); } + bindings.push(ObjectPatternElement::SingleName { + ident: *ident, + name: PropertyName::Literal(name), + default_init: Some(assign.rhs().clone()), + }); } else { return None; } diff --git a/boa_ast/src/expression/literal/template.rs b/boa_ast/src/expression/literal/template.rs index 7c3f4bc189c..e899c4564d2 100644 --- a/boa_ast/src/expression/literal/template.rs +++ b/boa_ast/src/expression/literal/template.rs @@ -60,7 +60,7 @@ impl TemplateLiteral { /// Gets the element list of this `TemplateLiteral`. #[must_use] - pub fn elements(&self) -> &[TemplateElement] { + pub const fn elements(&self) -> &[TemplateElement] { &self.elements } } @@ -116,8 +116,8 @@ impl VisitWith for TemplateElement { V: Visitor<'a>, { match self { - TemplateElement::String(sym) => visitor.visit_sym(sym), - TemplateElement::Expr(expr) => visitor.visit_expression(expr), + Self::String(sym) => visitor.visit_sym(sym), + Self::Expr(expr) => visitor.visit_expression(expr), } } @@ -126,8 +126,8 @@ impl VisitWith for TemplateElement { V: VisitorMut<'a>, { match self { - TemplateElement::String(sym) => visitor.visit_sym_mut(sym), - TemplateElement::Expr(expr) => visitor.visit_expression_mut(expr), + Self::String(sym) => visitor.visit_sym_mut(sym), + Self::Expr(expr) => visitor.visit_expression_mut(expr), } } } diff --git a/boa_ast/src/expression/mod.rs b/boa_ast/src/expression/mod.rs index ad048080947..47b389b8067 100644 --- a/boa_ast/src/expression/mod.rs +++ b/boa_ast/src/expression/mod.rs @@ -200,7 +200,7 @@ impl Expression { impl From for Statement { #[inline] fn from(expr: Expression) -> Self { - Statement::Expression(expr) + Self::Expression(expr) } } @@ -217,33 +217,33 @@ impl VisitWith for Expression { V: Visitor<'a>, { match self { - Expression::Identifier(id) => visitor.visit_identifier(id), - Expression::Literal(lit) => visitor.visit_literal(lit), - Expression::ArrayLiteral(arlit) => visitor.visit_array_literal(arlit), - Expression::ObjectLiteral(olit) => visitor.visit_object_literal(olit), - Expression::Spread(sp) => visitor.visit_spread(sp), - Expression::Function(f) => visitor.visit_function(f), - Expression::ArrowFunction(af) => visitor.visit_arrow_function(af), - Expression::AsyncArrowFunction(af) => visitor.visit_async_arrow_function(af), - Expression::Generator(g) => visitor.visit_generator(g), - Expression::AsyncFunction(af) => visitor.visit_async_function(af), - Expression::AsyncGenerator(ag) => visitor.visit_async_generator(ag), - Expression::Class(c) => visitor.visit_class(c), - Expression::TemplateLiteral(tlit) => visitor.visit_template_literal(tlit), - Expression::PropertyAccess(pa) => visitor.visit_property_access(pa), - Expression::New(n) => visitor.visit_new(n), - Expression::Call(c) => visitor.visit_call(c), - Expression::SuperCall(sc) => visitor.visit_super_call(sc), - Expression::Optional(opt) => visitor.visit_optional(opt), - Expression::TaggedTemplate(tt) => visitor.visit_tagged_template(tt), - Expression::Assign(a) => visitor.visit_assign(a), - Expression::Unary(u) => visitor.visit_unary(u), - Expression::Binary(b) => visitor.visit_binary(b), - Expression::Conditional(c) => visitor.visit_conditional(c), - Expression::Await(a) => visitor.visit_await(a), - Expression::Yield(y) => visitor.visit_yield(y), - Expression::FormalParameterList(fpl) => visitor.visit_formal_parameter_list(fpl), - Expression::This | Expression::NewTarget => { + Self::Identifier(id) => visitor.visit_identifier(id), + Self::Literal(lit) => visitor.visit_literal(lit), + Self::ArrayLiteral(arlit) => visitor.visit_array_literal(arlit), + Self::ObjectLiteral(olit) => visitor.visit_object_literal(olit), + Self::Spread(sp) => visitor.visit_spread(sp), + Self::Function(f) => visitor.visit_function(f), + Self::ArrowFunction(af) => visitor.visit_arrow_function(af), + Self::AsyncArrowFunction(af) => visitor.visit_async_arrow_function(af), + Self::Generator(g) => visitor.visit_generator(g), + Self::AsyncFunction(af) => visitor.visit_async_function(af), + Self::AsyncGenerator(ag) => visitor.visit_async_generator(ag), + Self::Class(c) => visitor.visit_class(c), + Self::TemplateLiteral(tlit) => visitor.visit_template_literal(tlit), + Self::PropertyAccess(pa) => visitor.visit_property_access(pa), + Self::New(n) => visitor.visit_new(n), + Self::Call(c) => visitor.visit_call(c), + Self::SuperCall(sc) => visitor.visit_super_call(sc), + Self::Optional(opt) => visitor.visit_optional(opt), + Self::TaggedTemplate(tt) => visitor.visit_tagged_template(tt), + Self::Assign(a) => visitor.visit_assign(a), + Self::Unary(u) => visitor.visit_unary(u), + Self::Binary(b) => visitor.visit_binary(b), + Self::Conditional(c) => visitor.visit_conditional(c), + Self::Await(a) => visitor.visit_await(a), + Self::Yield(y) => visitor.visit_yield(y), + Self::FormalParameterList(fpl) => visitor.visit_formal_parameter_list(fpl), + Self::This | Self::NewTarget => { // do nothing; can be handled as special case by visitor ControlFlow::Continue(()) } @@ -255,33 +255,33 @@ impl VisitWith for Expression { V: VisitorMut<'a>, { match self { - Expression::Identifier(id) => visitor.visit_identifier_mut(id), - Expression::Literal(lit) => visitor.visit_literal_mut(lit), - Expression::ArrayLiteral(arlit) => visitor.visit_array_literal_mut(arlit), - Expression::ObjectLiteral(olit) => visitor.visit_object_literal_mut(olit), - Expression::Spread(sp) => visitor.visit_spread_mut(sp), - Expression::Function(f) => visitor.visit_function_mut(f), - Expression::ArrowFunction(af) => visitor.visit_arrow_function_mut(af), - Expression::AsyncArrowFunction(af) => visitor.visit_async_arrow_function_mut(af), - Expression::Generator(g) => visitor.visit_generator_mut(g), - Expression::AsyncFunction(af) => visitor.visit_async_function_mut(af), - Expression::AsyncGenerator(ag) => visitor.visit_async_generator_mut(ag), - Expression::Class(c) => visitor.visit_class_mut(c), - Expression::TemplateLiteral(tlit) => visitor.visit_template_literal_mut(tlit), - Expression::PropertyAccess(pa) => visitor.visit_property_access_mut(pa), - Expression::New(n) => visitor.visit_new_mut(n), - Expression::Call(c) => visitor.visit_call_mut(c), - Expression::SuperCall(sc) => visitor.visit_super_call_mut(sc), - Expression::Optional(opt) => visitor.visit_optional_mut(opt), - Expression::TaggedTemplate(tt) => visitor.visit_tagged_template_mut(tt), - Expression::Assign(a) => visitor.visit_assign_mut(a), - Expression::Unary(u) => visitor.visit_unary_mut(u), - Expression::Binary(b) => visitor.visit_binary_mut(b), - Expression::Conditional(c) => visitor.visit_conditional_mut(c), - Expression::Await(a) => visitor.visit_await_mut(a), - Expression::Yield(y) => visitor.visit_yield_mut(y), - Expression::FormalParameterList(fpl) => visitor.visit_formal_parameter_list_mut(fpl), - Expression::This | Expression::NewTarget => { + Self::Identifier(id) => visitor.visit_identifier_mut(id), + Self::Literal(lit) => visitor.visit_literal_mut(lit), + Self::ArrayLiteral(arlit) => visitor.visit_array_literal_mut(arlit), + Self::ObjectLiteral(olit) => visitor.visit_object_literal_mut(olit), + Self::Spread(sp) => visitor.visit_spread_mut(sp), + Self::Function(f) => visitor.visit_function_mut(f), + Self::ArrowFunction(af) => visitor.visit_arrow_function_mut(af), + Self::AsyncArrowFunction(af) => visitor.visit_async_arrow_function_mut(af), + Self::Generator(g) => visitor.visit_generator_mut(g), + Self::AsyncFunction(af) => visitor.visit_async_function_mut(af), + Self::AsyncGenerator(ag) => visitor.visit_async_generator_mut(ag), + Self::Class(c) => visitor.visit_class_mut(c), + Self::TemplateLiteral(tlit) => visitor.visit_template_literal_mut(tlit), + Self::PropertyAccess(pa) => visitor.visit_property_access_mut(pa), + Self::New(n) => visitor.visit_new_mut(n), + Self::Call(c) => visitor.visit_call_mut(c), + Self::SuperCall(sc) => visitor.visit_super_call_mut(sc), + Self::Optional(opt) => visitor.visit_optional_mut(opt), + Self::TaggedTemplate(tt) => visitor.visit_tagged_template_mut(tt), + Self::Assign(a) => visitor.visit_assign_mut(a), + Self::Unary(u) => visitor.visit_unary_mut(u), + Self::Binary(b) => visitor.visit_binary_mut(b), + Self::Conditional(c) => visitor.visit_conditional_mut(c), + Self::Await(a) => visitor.visit_await_mut(a), + Self::Yield(y) => visitor.visit_yield_mut(y), + Self::FormalParameterList(fpl) => visitor.visit_formal_parameter_list_mut(fpl), + Self::This | Self::NewTarget => { // do nothing; can be handled as special case by visitor ControlFlow::Continue(()) } diff --git a/boa_ast/src/expression/new.rs b/boa_ast/src/expression/new.rs index f93e7a9bea2..4b77e8323e6 100644 --- a/boa_ast/src/expression/new.rs +++ b/boa_ast/src/expression/new.rs @@ -31,20 +31,20 @@ impl New { /// Gets the constructor of the new expression. #[inline] #[must_use] - pub fn constructor(&self) -> &Expression { + pub const fn constructor(&self) -> &Expression { self.call.function() } /// Retrieves the arguments passed to the constructor. #[inline] #[must_use] - pub fn arguments(&self) -> &[Expression] { + pub const fn arguments(&self) -> &[Expression] { self.call.args() } /// Returns the inner call expression. #[must_use] - pub fn call(&self) -> &Call { + pub const fn call(&self) -> &Call { &self.call } } diff --git a/boa_ast/src/expression/operator/assign/mod.rs b/boa_ast/src/expression/operator/assign/mod.rs index a2bbc692461..588c844946f 100644 --- a/boa_ast/src/expression/operator/assign/mod.rs +++ b/boa_ast/src/expression/operator/assign/mod.rs @@ -50,21 +50,21 @@ impl Assign { /// Gets the operator of the assignment operation. #[inline] #[must_use] - pub fn op(&self) -> AssignOp { + pub const fn op(&self) -> AssignOp { self.op } /// Gets the left hand side of the assignment operation. #[inline] #[must_use] - pub fn lhs(&self) -> &AssignTarget { + pub const fn lhs(&self) -> &AssignTarget { &self.lhs } /// Gets the right hand side of the assignment operation. #[inline] #[must_use] - pub fn rhs(&self) -> &Expression { + pub const fn rhs(&self) -> &Expression { &self.rhs } } @@ -151,9 +151,9 @@ impl ToInternedString for AssignTarget { #[inline] fn to_interned_string(&self, interner: &Interner) -> String { match self { - AssignTarget::Identifier(id) => id.to_interned_string(interner), - AssignTarget::Access(access) => access.to_interned_string(interner), - AssignTarget::Pattern(pattern) => pattern.to_interned_string(interner), + Self::Identifier(id) => id.to_interned_string(interner), + Self::Access(access) => access.to_interned_string(interner), + Self::Pattern(pattern) => pattern.to_interned_string(interner), } } } @@ -171,9 +171,9 @@ impl VisitWith for AssignTarget { V: Visitor<'a>, { match self { - AssignTarget::Identifier(id) => visitor.visit_identifier(id), - AssignTarget::Access(pa) => visitor.visit_property_access(pa), - AssignTarget::Pattern(pat) => visitor.visit_pattern(pat), + Self::Identifier(id) => visitor.visit_identifier(id), + Self::Access(pa) => visitor.visit_property_access(pa), + Self::Pattern(pat) => visitor.visit_pattern(pat), } } @@ -182,9 +182,9 @@ impl VisitWith for AssignTarget { V: VisitorMut<'a>, { match self { - AssignTarget::Identifier(id) => visitor.visit_identifier_mut(id), - AssignTarget::Access(pa) => visitor.visit_property_access_mut(pa), - AssignTarget::Pattern(pat) => visitor.visit_pattern_mut(pat), + Self::Identifier(id) => visitor.visit_identifier_mut(id), + Self::Access(pa) => visitor.visit_property_access_mut(pa), + Self::Pattern(pat) => visitor.visit_pattern_mut(pat), } } } diff --git a/boa_ast/src/expression/operator/assign/op.rs b/boa_ast/src/expression/operator/assign/op.rs index a2c25df8cf5..e574b1e9137 100644 --- a/boa_ast/src/expression/operator/assign/op.rs +++ b/boa_ast/src/expression/operator/assign/op.rs @@ -214,7 +214,7 @@ pub enum AssignOp { impl AssignOp { /// Retrieves the operation as a static string. - fn as_str(self) -> &'static str { + const fn as_str(self) -> &'static str { match self { Self::Assign => "=", Self::Add => "+=", diff --git a/boa_ast/src/expression/operator/binary/mod.rs b/boa_ast/src/expression/operator/binary/mod.rs index 2e280392641..c39466056fb 100644 --- a/boa_ast/src/expression/operator/binary/mod.rs +++ b/boa_ast/src/expression/operator/binary/mod.rs @@ -53,21 +53,21 @@ impl Binary { /// Gets the binary operation of the Expression. #[inline] #[must_use] - pub fn op(&self) -> BinaryOp { + pub const fn op(&self) -> BinaryOp { self.op } /// Gets the left hand side of the binary operation. #[inline] #[must_use] - pub fn lhs(&self) -> &Expression { + pub const fn lhs(&self) -> &Expression { &self.lhs } /// Gets the right hand side of the binary operation. #[inline] #[must_use] - pub fn rhs(&self) -> &Expression { + pub const fn rhs(&self) -> &Expression { &self.rhs } } diff --git a/boa_ast/src/expression/operator/binary/op.rs b/boa_ast/src/expression/operator/binary/op.rs index 60ace261298..9bba78e85b1 100644 --- a/boa_ast/src/expression/operator/binary/op.rs +++ b/boa_ast/src/expression/operator/binary/op.rs @@ -61,7 +61,7 @@ impl From for BinaryOp { impl BinaryOp { /// Retrieves the operation as a static string. - fn as_str(self) -> &'static str { + const fn as_str(self) -> &'static str { match self { Self::Arithmetic(ref op) => op.as_str(), Self::Bitwise(ref op) => op.as_str(), @@ -171,7 +171,7 @@ pub enum ArithmeticOp { impl ArithmeticOp { /// Retrieves the operation as a static string. - fn as_str(self) -> &'static str { + const fn as_str(self) -> &'static str { match self { Self::Add => "+", Self::Sub => "-", @@ -287,7 +287,7 @@ pub enum BitwiseOp { impl BitwiseOp { /// Retrieves the operation as a static string. - fn as_str(self) -> &'static str { + const fn as_str(self) -> &'static str { match self { Self::And => "&", Self::Or => "|", @@ -481,7 +481,7 @@ pub enum RelationalOp { impl RelationalOp { /// Retrieves the operation as a static string. - fn as_str(self) -> &'static str { + const fn as_str(self) -> &'static str { match self { Self::Equal => "==", Self::NotEqual => "!=", @@ -561,7 +561,7 @@ pub enum LogicalOp { impl LogicalOp { /// Retrieves the operation as a static string. - fn as_str(self) -> &'static str { + const fn as_str(self) -> &'static str { match self { Self::And => "&&", Self::Or => "||", diff --git a/boa_ast/src/expression/operator/conditional.rs b/boa_ast/src/expression/operator/conditional.rs index a0ac5711d61..f18b8d8f6a1 100644 --- a/boa_ast/src/expression/operator/conditional.rs +++ b/boa_ast/src/expression/operator/conditional.rs @@ -33,21 +33,21 @@ impl Conditional { /// Gets the condition of the `Conditional` expression. #[inline] #[must_use] - pub fn condition(&self) -> &Expression { + pub const fn condition(&self) -> &Expression { &self.condition } /// Gets the expression returned if `condition` is truthy. #[inline] #[must_use] - pub fn if_true(&self) -> &Expression { + pub const fn if_true(&self) -> &Expression { &self.if_true } /// Gets the expression returned if `condition` is falsy. #[inline] #[must_use] - pub fn if_false(&self) -> &Expression { + pub const fn if_false(&self) -> &Expression { &self.if_false } diff --git a/boa_ast/src/expression/operator/unary/mod.rs b/boa_ast/src/expression/operator/unary/mod.rs index ecf77b0f9c6..9c133e86e76 100644 --- a/boa_ast/src/expression/operator/unary/mod.rs +++ b/boa_ast/src/expression/operator/unary/mod.rs @@ -50,7 +50,7 @@ impl Unary { /// Gets the unary operation of the Expression. #[inline] #[must_use] - pub fn op(&self) -> UnaryOp { + pub const fn op(&self) -> UnaryOp { self.op } diff --git a/boa_ast/src/expression/operator/unary/op.rs b/boa_ast/src/expression/operator/unary/op.rs index ce3c7f607c9..f4e2461efd2 100644 --- a/boa_ast/src/expression/operator/unary/op.rs +++ b/boa_ast/src/expression/operator/unary/op.rs @@ -193,7 +193,7 @@ pub enum UnaryOp { impl UnaryOp { /// Retrieves the operation as a static string. - fn as_str(self) -> &'static str { + const fn as_str(self) -> &'static str { match self { Self::IncrementPost | Self::IncrementPre => "++", Self::DecrementPost | Self::DecrementPre => "--", diff --git a/boa_ast/src/expression/optional.rs b/boa_ast/src/expression/optional.rs index 7d7ff48cdd6..b7d16b3ea91 100644 --- a/boa_ast/src/expression/optional.rs +++ b/boa_ast/src/expression/optional.rs @@ -35,11 +35,9 @@ impl VisitWith for OptionalOperationKind { V: Visitor<'a>, { match self { - OptionalOperationKind::SimplePropertyAccess { field } => { - visitor.visit_property_access_field(field) - } - OptionalOperationKind::PrivatePropertyAccess { field } => visitor.visit_sym(field), - OptionalOperationKind::Call { args } => { + Self::SimplePropertyAccess { field } => visitor.visit_property_access_field(field), + Self::PrivatePropertyAccess { field } => visitor.visit_sym(field), + Self::Call { args } => { for arg in args.iter() { try_break!(visitor.visit_expression(arg)); } @@ -53,11 +51,9 @@ impl VisitWith for OptionalOperationKind { V: VisitorMut<'a>, { match self { - OptionalOperationKind::SimplePropertyAccess { field } => { - visitor.visit_property_access_field_mut(field) - } - OptionalOperationKind::PrivatePropertyAccess { field } => visitor.visit_sym_mut(field), - OptionalOperationKind::Call { args } => { + Self::SimplePropertyAccess { field } => visitor.visit_property_access_field_mut(field), + Self::PrivatePropertyAccess { field } => visitor.visit_sym_mut(field), + Self::Call { args } => { for arg in args.iter_mut() { try_break!(visitor.visit_expression_mut(arg)); } @@ -85,13 +81,13 @@ impl OptionalOperation { /// Creates a new `OptionalOperation`. #[inline] #[must_use] - pub fn new(kind: OptionalOperationKind, shorted: bool) -> Self { + pub const fn new(kind: OptionalOperationKind, shorted: bool) -> Self { Self { kind, shorted } } /// Gets the kind of operation. #[inline] #[must_use] - pub fn kind(&self) -> &OptionalOperationKind { + pub const fn kind(&self) -> &OptionalOperationKind { &self.kind } @@ -99,7 +95,7 @@ impl OptionalOperation { /// `undefined` or `null`. #[inline] #[must_use] - pub fn shorted(&self) -> bool { + pub const fn shorted(&self) -> bool { self.shorted } } @@ -236,7 +232,7 @@ impl Optional { impl From for Expression { fn from(opt: Optional) -> Self { - Expression::Optional(opt) + Self::Optional(opt) } } diff --git a/boa_ast/src/expression/spread.rs b/boa_ast/src/expression/spread.rs index fc86dde42ad..95d4ed4b17e 100644 --- a/boa_ast/src/expression/spread.rs +++ b/boa_ast/src/expression/spread.rs @@ -33,7 +33,7 @@ impl Spread { /// Gets the target expression to be expanded by the spread operator. #[inline] #[must_use] - pub fn target(&self) -> &Expression { + pub const fn target(&self) -> &Expression { &self.target } diff --git a/boa_ast/src/expression/tagged_template.rs b/boa_ast/src/expression/tagged_template.rs index 2a01bbbf200..68d79f61ac1 100644 --- a/boa_ast/src/expression/tagged_template.rs +++ b/boa_ast/src/expression/tagged_template.rs @@ -45,28 +45,28 @@ impl TaggedTemplate { /// Gets the tag function of the template. #[inline] #[must_use] - pub fn tag(&self) -> &Expression { + pub const fn tag(&self) -> &Expression { &self.tag } /// Gets the inner raw strings of the template. #[inline] #[must_use] - pub fn raws(&self) -> &[Sym] { + pub const fn raws(&self) -> &[Sym] { &self.raws } /// Gets the cooked strings of the template. #[inline] #[must_use] - pub fn cookeds(&self) -> &[Option] { + pub const fn cookeds(&self) -> &[Option] { &self.cookeds } /// Gets the interpolated expressions of the template. #[inline] #[must_use] - pub fn exprs(&self) -> &[Expression] { + pub const fn exprs(&self) -> &[Expression] { &self.exprs } } diff --git a/boa_ast/src/expression/yield.rs b/boa_ast/src/expression/yield.rs index 7357e00c64d..91c2cd01b4a 100644 --- a/boa_ast/src/expression/yield.rs +++ b/boa_ast/src/expression/yield.rs @@ -31,7 +31,7 @@ impl Yield { /// Returns `true` if this `Yield` statement delegates to another generator or iterable object. #[inline] #[must_use] - pub fn delegate(&self) -> bool { + pub const fn delegate(&self) -> bool { self.delegate } diff --git a/boa_ast/src/function/arrow_function.rs b/boa_ast/src/function/arrow_function.rs index c02c3a8e2a3..0c5a5b5b85d 100644 --- a/boa_ast/src/function/arrow_function.rs +++ b/boa_ast/src/function/arrow_function.rs @@ -31,7 +31,11 @@ impl ArrowFunction { /// Creates a new `ArrowFunctionDecl` AST Expression. #[inline] #[must_use] - pub fn new(name: Option, params: FormalParameterList, body: StatementList) -> Self { + pub const fn new( + name: Option, + params: FormalParameterList, + body: StatementList, + ) -> Self { Self { name, parameters: params, @@ -42,7 +46,7 @@ impl ArrowFunction { /// Gets the name of the function declaration. #[inline] #[must_use] - pub fn name(&self) -> Option { + pub const fn name(&self) -> Option { self.name } @@ -55,14 +59,14 @@ impl ArrowFunction { /// Gets the list of parameters of the arrow function. #[inline] #[must_use] - pub fn parameters(&self) -> &FormalParameterList { + pub const fn parameters(&self) -> &FormalParameterList { &self.parameters } /// Gets the body of the arrow function. #[inline] #[must_use] - pub fn body(&self) -> &StatementList { + pub const fn body(&self) -> &StatementList { &self.body } } diff --git a/boa_ast/src/function/async_arrow_function.rs b/boa_ast/src/function/async_arrow_function.rs index 191de2f5422..378dbfe8c07 100644 --- a/boa_ast/src/function/async_arrow_function.rs +++ b/boa_ast/src/function/async_arrow_function.rs @@ -31,7 +31,7 @@ impl AsyncArrowFunction { /// Creates a new `AsyncArrowFunction` AST Expression. #[inline] #[must_use] - pub fn new( + pub const fn new( name: Option, parameters: FormalParameterList, body: StatementList, @@ -46,7 +46,7 @@ impl AsyncArrowFunction { /// Gets the name of the function declaration. #[inline] #[must_use] - pub fn name(&self) -> Option { + pub const fn name(&self) -> Option { self.name } @@ -60,14 +60,14 @@ impl AsyncArrowFunction { /// Gets the list of parameters of the arrow function. #[inline] #[must_use] - pub fn parameters(&self) -> &FormalParameterList { + pub const fn parameters(&self) -> &FormalParameterList { &self.parameters } /// Gets the body of the arrow function. #[inline] #[must_use] - pub fn body(&self) -> &StatementList { + pub const fn body(&self) -> &StatementList { &self.body } } diff --git a/boa_ast/src/function/async_function.rs b/boa_ast/src/function/async_function.rs index 17df80504f8..74100ee8739 100644 --- a/boa_ast/src/function/async_function.rs +++ b/boa_ast/src/function/async_function.rs @@ -33,7 +33,7 @@ impl AsyncFunction { /// Creates a new function expression #[inline] #[must_use] - pub fn new( + pub const fn new( name: Option, parameters: FormalParameterList, body: StatementList, @@ -50,28 +50,28 @@ impl AsyncFunction { /// Gets the name of the function declaration. #[inline] #[must_use] - pub fn name(&self) -> Option { + pub const fn name(&self) -> Option { self.name } /// Gets the list of parameters of the function declaration. #[inline] #[must_use] - pub fn parameters(&self) -> &FormalParameterList { + pub const fn parameters(&self) -> &FormalParameterList { &self.parameters } /// Gets the body of the function declaration. #[inline] #[must_use] - pub fn body(&self) -> &StatementList { + pub const fn body(&self) -> &StatementList { &self.body } /// Returns whether the function expression has a binding identifier. #[inline] #[must_use] - pub fn has_binding_identifier(&self) -> bool { + pub const fn has_binding_identifier(&self) -> bool { self.has_binding_identifier } } diff --git a/boa_ast/src/function/async_generator.rs b/boa_ast/src/function/async_generator.rs index 420a8bbd23f..f98309237af 100644 --- a/boa_ast/src/function/async_generator.rs +++ b/boa_ast/src/function/async_generator.rs @@ -32,7 +32,7 @@ impl AsyncGenerator { /// Creates a new async generator expression #[inline] #[must_use] - pub fn new( + pub const fn new( name: Option, parameters: FormalParameterList, body: StatementList, @@ -49,28 +49,28 @@ impl AsyncGenerator { /// Gets the name of the async generator expression #[inline] #[must_use] - pub fn name(&self) -> Option { + pub const fn name(&self) -> Option { self.name } /// Gets the list of parameters of the async generator expression #[inline] #[must_use] - pub fn parameters(&self) -> &FormalParameterList { + pub const fn parameters(&self) -> &FormalParameterList { &self.parameters } /// Gets the body of the async generator expression #[inline] #[must_use] - pub fn body(&self) -> &StatementList { + pub const fn body(&self) -> &StatementList { &self.body } /// Returns whether the function expression has a binding identifier. #[inline] #[must_use] - pub fn has_binding_identifier(&self) -> bool { + pub const fn has_binding_identifier(&self) -> bool { self.has_binding_identifier } } diff --git a/boa_ast/src/function/class.rs b/boa_ast/src/function/class.rs index dea5811eedf..1106371932d 100644 --- a/boa_ast/src/function/class.rs +++ b/boa_ast/src/function/class.rs @@ -52,28 +52,28 @@ impl Class { /// Returns the name of the class. #[inline] #[must_use] - pub fn name(&self) -> Option { + pub const fn name(&self) -> Option { self.name } /// Returns the super class ref of the class. #[inline] #[must_use] - pub fn super_ref(&self) -> Option<&Expression> { + pub const fn super_ref(&self) -> Option<&Expression> { self.super_ref.as_ref() } /// Returns the constructor of the class. #[inline] #[must_use] - pub fn constructor(&self) -> Option<&Function> { + pub const fn constructor(&self) -> Option<&Function> { self.constructor.as_ref() } /// Gets the list of all fields defined on the class. #[inline] #[must_use] - pub fn elements(&self) -> &[ClassElement] { + pub const fn elements(&self) -> &[ClassElement] { &self.elements } } @@ -90,21 +90,23 @@ impl ToIndentedString for Class { if self.elements.is_empty() && self.constructor().is_none() { return format!( "class {class_name}{} {{}}", - if let Some(sup) = &self.super_ref { - format!(" extends {}", sup.to_interned_string(interner)) - } else { - String::new() - } + self.super_ref + .as_ref() + .map_or_else(String::new, |sup| format!( + " extends {}", + sup.to_interned_string(interner) + )) ); } let indentation = " ".repeat(indent_n + 1); let mut buf = format!( "class {class_name}{} {{\n", - if let Some(sup) = &self.super_ref { - format!("extends {}", sup.to_interned_string(interner)) - } else { - String::new() - } + self.super_ref + .as_ref() + .map_or_else(String::new, |sup| format!( + "extends {}", + sup.to_interned_string(interner) + )) ); if let Some(expr) = &self.constructor { buf.push_str(&format!( @@ -438,13 +440,11 @@ impl VisitWith for ClassElement { V: Visitor<'a>, { match self { - ClassElement::MethodDefinition(pn, md) - | ClassElement::StaticMethodDefinition(pn, md) => { + Self::MethodDefinition(pn, md) | Self::StaticMethodDefinition(pn, md) => { try_break!(visitor.visit_property_name(pn)); visitor.visit_method_definition(md) } - ClassElement::FieldDefinition(pn, maybe_expr) - | ClassElement::StaticFieldDefinition(pn, maybe_expr) => { + Self::FieldDefinition(pn, maybe_expr) | Self::StaticFieldDefinition(pn, maybe_expr) => { try_break!(visitor.visit_property_name(pn)); if let Some(expr) = maybe_expr { visitor.visit_expression(expr) @@ -452,13 +452,13 @@ impl VisitWith for ClassElement { ControlFlow::Continue(()) } } - ClassElement::PrivateMethodDefinition(sym, md) - | ClassElement::PrivateStaticMethodDefinition(sym, md) => { + Self::PrivateMethodDefinition(sym, md) + | Self::PrivateStaticMethodDefinition(sym, md) => { try_break!(visitor.visit_sym(sym)); visitor.visit_method_definition(md) } - ClassElement::PrivateFieldDefinition(sym, maybe_expr) - | ClassElement::PrivateStaticFieldDefinition(sym, maybe_expr) => { + Self::PrivateFieldDefinition(sym, maybe_expr) + | Self::PrivateStaticFieldDefinition(sym, maybe_expr) => { try_break!(visitor.visit_sym(sym)); if let Some(expr) = maybe_expr { visitor.visit_expression(expr) @@ -466,7 +466,7 @@ impl VisitWith for ClassElement { ControlFlow::Continue(()) } } - ClassElement::StaticBlock(sl) => visitor.visit_statement_list(sl), + Self::StaticBlock(sl) => visitor.visit_statement_list(sl), } } @@ -475,13 +475,11 @@ impl VisitWith for ClassElement { V: VisitorMut<'a>, { match self { - ClassElement::MethodDefinition(pn, md) - | ClassElement::StaticMethodDefinition(pn, md) => { + Self::MethodDefinition(pn, md) | Self::StaticMethodDefinition(pn, md) => { try_break!(visitor.visit_property_name_mut(pn)); visitor.visit_method_definition_mut(md) } - ClassElement::FieldDefinition(pn, maybe_expr) - | ClassElement::StaticFieldDefinition(pn, maybe_expr) => { + Self::FieldDefinition(pn, maybe_expr) | Self::StaticFieldDefinition(pn, maybe_expr) => { try_break!(visitor.visit_property_name_mut(pn)); if let Some(expr) = maybe_expr { visitor.visit_expression_mut(expr) @@ -489,13 +487,13 @@ impl VisitWith for ClassElement { ControlFlow::Continue(()) } } - ClassElement::PrivateMethodDefinition(sym, md) - | ClassElement::PrivateStaticMethodDefinition(sym, md) => { + Self::PrivateMethodDefinition(sym, md) + | Self::PrivateStaticMethodDefinition(sym, md) => { try_break!(visitor.visit_sym_mut(sym)); visitor.visit_method_definition_mut(md) } - ClassElement::PrivateFieldDefinition(sym, maybe_expr) - | ClassElement::PrivateStaticFieldDefinition(sym, maybe_expr) => { + Self::PrivateFieldDefinition(sym, maybe_expr) + | Self::PrivateStaticFieldDefinition(sym, maybe_expr) => { try_break!(visitor.visit_sym_mut(sym)); if let Some(expr) = maybe_expr { visitor.visit_expression_mut(expr) @@ -503,7 +501,7 @@ impl VisitWith for ClassElement { ControlFlow::Continue(()) } } - ClassElement::StaticBlock(sl) => visitor.visit_statement_list_mut(sl), + Self::StaticBlock(sl) => visitor.visit_statement_list_mut(sl), } } } diff --git a/boa_ast/src/function/generator.rs b/boa_ast/src/function/generator.rs index 5478633d46e..9f18623c38b 100644 --- a/boa_ast/src/function/generator.rs +++ b/boa_ast/src/function/generator.rs @@ -34,7 +34,7 @@ impl Generator { /// Creates a new generator expression #[inline] #[must_use] - pub fn new( + pub const fn new( name: Option, parameters: FormalParameterList, body: StatementList, @@ -51,28 +51,28 @@ impl Generator { /// Gets the name of the generator declaration. #[inline] #[must_use] - pub fn name(&self) -> Option { + pub const fn name(&self) -> Option { self.name } /// Gets the list of parameters of the generator declaration. #[inline] #[must_use] - pub fn parameters(&self) -> &FormalParameterList { + pub const fn parameters(&self) -> &FormalParameterList { &self.parameters } /// Gets the body of the generator declaration. #[inline] #[must_use] - pub fn body(&self) -> &StatementList { + pub const fn body(&self) -> &StatementList { &self.body } /// Returns whether the function expression has a binding identifier. #[inline] #[must_use] - pub fn has_binding_identifier(&self) -> bool { + pub const fn has_binding_identifier(&self) -> bool { self.has_binding_identifier } } diff --git a/boa_ast/src/function/mod.rs b/boa_ast/src/function/mod.rs index 4aa1d06c395..c043f07b248 100644 --- a/boa_ast/src/function/mod.rs +++ b/boa_ast/src/function/mod.rs @@ -70,7 +70,7 @@ impl Function { /// Creates a new function expression. #[inline] #[must_use] - pub fn new( + pub const fn new( name: Option, parameters: FormalParameterList, body: StatementList, @@ -86,7 +86,7 @@ impl Function { /// Creates a new function expression with an expression binding identifier. #[inline] #[must_use] - pub fn new_with_binding_identifier( + pub const fn new_with_binding_identifier( name: Option, parameters: FormalParameterList, body: StatementList, @@ -103,28 +103,28 @@ impl Function { /// Gets the name of the function declaration. #[inline] #[must_use] - pub fn name(&self) -> Option { + pub const fn name(&self) -> Option { self.name } /// Gets the list of parameters of the function declaration. #[inline] #[must_use] - pub fn parameters(&self) -> &FormalParameterList { + pub const fn parameters(&self) -> &FormalParameterList { &self.parameters } /// Gets the body of the function declaration. #[inline] #[must_use] - pub fn body(&self) -> &StatementList { + pub const fn body(&self) -> &StatementList { &self.body } /// Returns whether the function expression has a binding identifier. #[inline] #[must_use] - pub fn has_binding_identifier(&self) -> bool { + pub const fn has_binding_identifier(&self) -> bool { self.has_binding_identifier } } diff --git a/boa_ast/src/function/parameters.rs b/boa_ast/src/function/parameters.rs index 9d80ebd86b7..eba5b034fe9 100644 --- a/boa_ast/src/function/parameters.rs +++ b/boa_ast/src/function/parameters.rs @@ -81,46 +81,46 @@ impl FormalParameterList { /// Returns the length of the parameter list. /// Note that this is not equal to the length of the parameters slice. #[must_use] - pub fn length(&self) -> u32 { + pub const fn length(&self) -> u32 { self.length } /// Returns the parameter list flags. #[must_use] - pub fn flags(&self) -> FormalParameterListFlags { + pub const fn flags(&self) -> FormalParameterListFlags { self.flags } /// Indicates if the parameter list is simple. #[must_use] - pub fn is_simple(&self) -> bool { + pub const fn is_simple(&self) -> bool { self.flags.contains(FormalParameterListFlags::IS_SIMPLE) } /// Indicates if the parameter list has duplicate parameters. #[must_use] - pub fn has_duplicates(&self) -> bool { + pub const fn has_duplicates(&self) -> bool { self.flags .contains(FormalParameterListFlags::HAS_DUPLICATES) } /// Indicates if the parameter list has a rest parameter. #[must_use] - pub fn has_rest_parameter(&self) -> bool { + pub const fn has_rest_parameter(&self) -> bool { self.flags .contains(FormalParameterListFlags::HAS_REST_PARAMETER) } /// Indicates if the parameter list has expressions in it's parameters. #[must_use] - pub fn has_expressions(&self) -> bool { + pub const fn has_expressions(&self) -> bool { self.flags .contains(FormalParameterListFlags::HAS_EXPRESSIONS) } /// Indicates if the parameter list has parameters named 'arguments'. #[must_use] - pub fn has_arguments(&self) -> bool { + pub const fn has_arguments(&self) -> bool { self.flags.contains(FormalParameterListFlags::HAS_ARGUMENTS) } } @@ -235,25 +235,25 @@ impl FormalParameter { /// Gets the variable of the formal parameter #[must_use] - pub fn variable(&self) -> &Variable { + pub const fn variable(&self) -> &Variable { &self.variable } /// Gets the initialization node of the formal parameter, if any. #[must_use] - pub fn init(&self) -> Option<&Expression> { + pub const fn init(&self) -> Option<&Expression> { self.variable.init() } /// Returns `true` if the parameter is a rest parameter. #[must_use] - pub fn is_rest_param(&self) -> bool { + pub const fn is_rest_param(&self) -> bool { self.is_rest_param } /// Returns `true` if the parameter is an identifier. #[must_use] - pub fn is_identifier(&self) -> bool { + pub const fn is_identifier(&self) -> bool { matches!(&self.variable.binding(), Binding::Identifier(_)) } } diff --git a/boa_ast/src/keyword.rs b/boa_ast/src/keyword.rs index a59527929b6..a90ac7f73be 100644 --- a/boa_ast/src/keyword.rs +++ b/boa_ast/src/keyword.rs @@ -478,7 +478,7 @@ impl Keyword { /// Gets the keyword as a binary operation, if this keyword is the `in` or the `instanceof` /// keywords. #[must_use] - pub fn as_binary_op(self) -> Option { + pub const fn as_binary_op(self) -> Option { match self { Self::In => Some(BinaryOp::Relational(RelationalOp::In)), Self::InstanceOf => Some(BinaryOp::Relational(RelationalOp::InstanceOf)), @@ -488,7 +488,7 @@ impl Keyword { /// Gets the keyword as a tuple of strings. #[must_use] - pub fn as_str(self) -> (&'static str, &'static [u16]) { + pub const fn as_str(self) -> (&'static str, &'static [u16]) { match self { Self::Await => ("await", utf16!("await")), Self::Async => ("async", utf16!("async")), diff --git a/boa_ast/src/lib.rs b/boa_ast/src/lib.rs index c9781d2b546..61bb575bcca 100644 --- a/boa_ast/src/lib.rs +++ b/boa_ast/src/lib.rs @@ -12,44 +12,63 @@ //! [early]: https://tc39.es/ecma262/#sec-static-semantic-rules #![cfg_attr(not(test), forbid(clippy::unwrap_used))] -#![warn( - clippy::perf, - clippy::single_match_else, - clippy::dbg_macro, - clippy::doc_markdown, - clippy::wildcard_imports, - clippy::struct_excessive_bools, - clippy::doc_markdown, - clippy::semicolon_if_nothing_returned, - clippy::pedantic -)] +#![warn(missing_docs, clippy::dbg_macro)] #![deny( - clippy::all, - clippy::cast_lossless, - clippy::redundant_closure_for_method_calls, - clippy::unnested_or_patterns, - clippy::trivially_copy_pass_by_ref, - clippy::needless_pass_by_value, - clippy::match_wildcard_for_single_variants, - clippy::map_unwrap_or, - unused_qualifications, + // rustc lint groups https://doc.rust-lang.org/rustc/lints/groups.html + warnings, + future_incompatible, + let_underscore, + nonstandard_style, + rust_2018_compatibility, + rust_2018_idioms, + rust_2021_compatibility, + unused, + + // rustc allowed-by-default lints https://doc.rust-lang.org/rustc/lints/listing/allowed-by-default.html + macro_use_extern_crate, + meta_variable_misuse, + missing_abi, + missing_copy_implementations, + missing_debug_implementations, + non_ascii_idents, + noop_method_call, + single_use_lifetimes, + trivial_casts, + trivial_numeric_casts, + unreachable_pub, + unsafe_op_in_unsafe_fn, + unused_crate_dependencies, unused_import_braces, unused_lifetimes, - unreachable_pub, - trivial_numeric_casts, + unused_qualifications, + unused_tuple_struct_fields, + variant_size_differences, + + // rustdoc lints https://doc.rust-lang.org/rustdoc/lints.html rustdoc::broken_intra_doc_links, - missing_debug_implementations, - missing_copy_implementations, - deprecated_in_future, - meta_variable_misuse, - non_ascii_idents, - rust_2018_compatibility, - rust_2018_idioms, - future_incompatible, - nonstandard_style, - missing_docs + rustdoc::private_intra_doc_links, + rustdoc::missing_crate_level_docs, + rustdoc::private_doc_tests, + rustdoc::invalid_codeblock_attributes, + rustdoc::invalid_rust_codeblocks, + rustdoc::bare_urls, + + // clippy categories https://doc.rust-lang.org/clippy/ + clippy::all, + clippy::correctness, + clippy::suspicious, + clippy::style, + clippy::complexity, + clippy::perf, + clippy::pedantic, + clippy::nursery, +)] +#![allow( + clippy::module_name_repetitions, + clippy::too_many_lines, + clippy::option_if_let_else, + clippy::use_self )] -#![allow(clippy::module_name_repetitions, clippy::too_many_lines)] mod position; mod punctuator; diff --git a/boa_ast/src/pattern.rs b/boa_ast/src/pattern.rs index bd9b8694306..a8b1644b33c 100644 --- a/boa_ast/src/pattern.rs +++ b/boa_ast/src/pattern.rs @@ -47,13 +47,13 @@ pub enum Pattern { impl From for Pattern { fn from(obj: ObjectPattern) -> Self { - Pattern::Object(obj) + Self::Object(obj) } } impl From for Pattern { fn from(obj: ArrayPattern) -> Self { - Pattern::Array(obj) + Self::Array(obj) } } @@ -71,8 +71,8 @@ impl From> for Pattern { impl ToInternedString for Pattern { fn to_interned_string(&self, interner: &Interner) -> String { match &self { - Pattern::Object(o) => o.to_interned_string(interner), - Pattern::Array(a) => a.to_interned_string(interner), + Self::Object(o) => o.to_interned_string(interner), + Self::Array(a) => a.to_interned_string(interner), } } } @@ -83,8 +83,8 @@ impl VisitWith for Pattern { V: Visitor<'a>, { match self { - Pattern::Object(op) => visitor.visit_object_pattern(op), - Pattern::Array(ap) => visitor.visit_array_pattern(ap), + Self::Object(op) => visitor.visit_object_pattern(op), + Self::Array(ap) => visitor.visit_array_pattern(ap), } } @@ -93,8 +93,8 @@ impl VisitWith for Pattern { V: VisitorMut<'a>, { match self { - Pattern::Object(op) => visitor.visit_object_pattern_mut(op), - Pattern::Array(ap) => visitor.visit_array_pattern_mut(ap), + Self::Object(op) => visitor.visit_object_pattern_mut(op), + Self::Array(ap) => visitor.visit_array_pattern_mut(ap), } } } @@ -151,14 +151,14 @@ impl ObjectPattern { /// Gets the bindings for the object binding pattern. #[inline] #[must_use] - pub fn bindings(&self) -> &[ObjectPatternElement] { + pub const fn bindings(&self) -> &[ObjectPatternElement] { &self.0 } /// Returns true if the object binding pattern has a rest element. #[inline] #[must_use] - pub fn has_rest(&self) -> bool { + pub const fn has_rest(&self) -> bool { matches!( self.0.last(), Some(ObjectPatternElement::RestProperty { .. }) @@ -239,7 +239,7 @@ impl ArrayPattern { /// Gets the bindings for the array binding pattern. #[inline] #[must_use] - pub fn bindings(&self) -> &[ArrayPatternElement] { + pub const fn bindings(&self) -> &[ArrayPatternElement] { &self.0 } } @@ -468,7 +468,7 @@ impl VisitWith for ObjectPatternElement { V: Visitor<'a>, { match self { - ObjectPatternElement::SingleName { + Self::SingleName { name, ident, default_init, @@ -481,8 +481,8 @@ impl VisitWith for ObjectPatternElement { ControlFlow::Continue(()) } } - ObjectPatternElement::RestProperty { ident, .. } => visitor.visit_identifier(ident), - ObjectPatternElement::AssignmentPropertyAccess { + Self::RestProperty { ident, .. } => visitor.visit_identifier(ident), + Self::AssignmentPropertyAccess { name, access, default_init, @@ -495,10 +495,10 @@ impl VisitWith for ObjectPatternElement { ControlFlow::Continue(()) } } - ObjectPatternElement::AssignmentRestPropertyAccess { access, .. } => { + Self::AssignmentRestPropertyAccess { access, .. } => { visitor.visit_property_access(access) } - ObjectPatternElement::Pattern { + Self::Pattern { name, pattern, default_init, @@ -519,7 +519,7 @@ impl VisitWith for ObjectPatternElement { V: VisitorMut<'a>, { match self { - ObjectPatternElement::SingleName { + Self::SingleName { name, ident, default_init, @@ -532,8 +532,8 @@ impl VisitWith for ObjectPatternElement { ControlFlow::Continue(()) } } - ObjectPatternElement::RestProperty { ident, .. } => visitor.visit_identifier_mut(ident), - ObjectPatternElement::AssignmentPropertyAccess { + Self::RestProperty { ident, .. } => visitor.visit_identifier_mut(ident), + Self::AssignmentPropertyAccess { name, access, default_init, @@ -546,10 +546,10 @@ impl VisitWith for ObjectPatternElement { ControlFlow::Continue(()) } } - ObjectPatternElement::AssignmentRestPropertyAccess { access, .. } => { + Self::AssignmentRestPropertyAccess { access, .. } => { visitor.visit_property_access_mut(access) } - ObjectPatternElement::Pattern { + Self::Pattern { name, pattern, default_init, @@ -712,7 +712,7 @@ impl VisitWith for ArrayPatternElement { V: Visitor<'a>, { match self { - ArrayPatternElement::SingleName { + Self::SingleName { ident, default_init, } => { @@ -723,11 +723,10 @@ impl VisitWith for ArrayPatternElement { ControlFlow::Continue(()) } } - ArrayPatternElement::PropertyAccess { access } - | ArrayPatternElement::PropertyAccessRest { access } => { + Self::PropertyAccess { access } | Self::PropertyAccessRest { access } => { visitor.visit_property_access(access) } - ArrayPatternElement::Pattern { + Self::Pattern { pattern, default_init, } => { @@ -738,9 +737,9 @@ impl VisitWith for ArrayPatternElement { ControlFlow::Continue(()) } } - ArrayPatternElement::SingleNameRest { ident } => visitor.visit_identifier(ident), - ArrayPatternElement::PatternRest { pattern } => visitor.visit_pattern(pattern), - ArrayPatternElement::Elision => { + Self::SingleNameRest { ident } => visitor.visit_identifier(ident), + Self::PatternRest { pattern } => visitor.visit_pattern(pattern), + Self::Elision => { // special case to be handled by user ControlFlow::Continue(()) } @@ -752,7 +751,7 @@ impl VisitWith for ArrayPatternElement { V: VisitorMut<'a>, { match self { - ArrayPatternElement::SingleName { + Self::SingleName { ident, default_init, } => { @@ -763,11 +762,10 @@ impl VisitWith for ArrayPatternElement { ControlFlow::Continue(()) } } - ArrayPatternElement::PropertyAccess { access } - | ArrayPatternElement::PropertyAccessRest { access } => { + Self::PropertyAccess { access } | Self::PropertyAccessRest { access } => { visitor.visit_property_access_mut(access) } - ArrayPatternElement::Pattern { + Self::Pattern { pattern, default_init, } => { @@ -778,9 +776,9 @@ impl VisitWith for ArrayPatternElement { ControlFlow::Continue(()) } } - ArrayPatternElement::SingleNameRest { ident } => visitor.visit_identifier_mut(ident), - ArrayPatternElement::PatternRest { pattern } => visitor.visit_pattern_mut(pattern), - ArrayPatternElement::Elision => { + Self::SingleNameRest { ident } => visitor.visit_identifier_mut(ident), + Self::PatternRest { pattern } => visitor.visit_pattern_mut(pattern), + Self::Elision => { // special case to be handled by user ControlFlow::Continue(()) } diff --git a/boa_ast/src/position.rs b/boa_ast/src/position.rs index dafba2fb30c..5c7a0956b4b 100644 --- a/boa_ast/src/position.rs +++ b/boa_ast/src/position.rs @@ -30,14 +30,14 @@ impl Position { /// Gets the line number of the position. #[inline] #[must_use] - pub fn line_number(self) -> u32 { + pub const fn line_number(self) -> u32 { self.line_number.get() } /// Gets the column number of the position. #[inline] #[must_use] - pub fn column_number(self) -> u32 { + pub const fn column_number(self) -> u32 { self.column_number.get() } } @@ -79,14 +79,14 @@ impl Span { /// Gets the starting position of the span. #[inline] #[must_use] - pub fn start(self) -> Position { + pub const fn start(self) -> Position { self.start } /// Gets the final position of the span. #[inline] #[must_use] - pub fn end(self) -> Position { + pub const fn end(self) -> Position { self.end } diff --git a/boa_ast/src/property.rs b/boa_ast/src/property.rs index c6537199038..6f5b5d063fd 100644 --- a/boa_ast/src/property.rs +++ b/boa_ast/src/property.rs @@ -86,17 +86,17 @@ impl VisitWith for PropertyDefinition { V: Visitor<'a>, { match self { - PropertyDefinition::IdentifierReference(id) => visitor.visit_identifier(id), - PropertyDefinition::Property(pn, expr) => { + Self::IdentifierReference(id) => visitor.visit_identifier(id), + Self::Property(pn, expr) => { try_break!(visitor.visit_property_name(pn)); visitor.visit_expression(expr) } - PropertyDefinition::MethodDefinition(pn, md) => { + Self::MethodDefinition(pn, md) => { try_break!(visitor.visit_property_name(pn)); visitor.visit_method_definition(md) } - PropertyDefinition::SpreadObject(expr) => visitor.visit_expression(expr), - PropertyDefinition::CoverInitializedName(id, expr) => { + Self::SpreadObject(expr) => visitor.visit_expression(expr), + Self::CoverInitializedName(id, expr) => { try_break!(visitor.visit_identifier(id)); visitor.visit_expression(expr) } @@ -108,17 +108,17 @@ impl VisitWith for PropertyDefinition { V: VisitorMut<'a>, { match self { - PropertyDefinition::IdentifierReference(id) => visitor.visit_identifier_mut(id), - PropertyDefinition::Property(pn, expr) => { + Self::IdentifierReference(id) => visitor.visit_identifier_mut(id), + Self::Property(pn, expr) => { try_break!(visitor.visit_property_name_mut(pn)); visitor.visit_expression_mut(expr) } - PropertyDefinition::MethodDefinition(pn, md) => { + Self::MethodDefinition(pn, md) => { try_break!(visitor.visit_property_name_mut(pn)); visitor.visit_method_definition_mut(md) } - PropertyDefinition::SpreadObject(expr) => visitor.visit_expression_mut(expr), - PropertyDefinition::CoverInitializedName(id, expr) => { + Self::SpreadObject(expr) => visitor.visit_expression_mut(expr), + Self::CoverInitializedName(id, expr) => { try_break!(visitor.visit_identifier_mut(id)); visitor.visit_expression_mut(expr) } @@ -219,12 +219,10 @@ impl VisitWith for MethodDefinition { V: Visitor<'a>, { match self { - MethodDefinition::Get(f) | MethodDefinition::Set(f) | MethodDefinition::Ordinary(f) => { - visitor.visit_function(f) - } - MethodDefinition::Generator(g) => visitor.visit_generator(g), - MethodDefinition::AsyncGenerator(ag) => visitor.visit_async_generator(ag), - MethodDefinition::Async(af) => visitor.visit_async_function(af), + Self::Get(f) | Self::Set(f) | Self::Ordinary(f) => visitor.visit_function(f), + Self::Generator(g) => visitor.visit_generator(g), + Self::AsyncGenerator(ag) => visitor.visit_async_generator(ag), + Self::Async(af) => visitor.visit_async_function(af), } } @@ -233,12 +231,10 @@ impl VisitWith for MethodDefinition { V: VisitorMut<'a>, { match self { - MethodDefinition::Get(f) | MethodDefinition::Set(f) | MethodDefinition::Ordinary(f) => { - visitor.visit_function_mut(f) - } - MethodDefinition::Generator(g) => visitor.visit_generator_mut(g), - MethodDefinition::AsyncGenerator(ag) => visitor.visit_async_generator_mut(ag), - MethodDefinition::Async(af) => visitor.visit_async_function_mut(af), + Self::Get(f) | Self::Set(f) | Self::Ordinary(f) => visitor.visit_function_mut(f), + Self::Generator(g) => visitor.visit_generator_mut(g), + Self::AsyncGenerator(ag) => visitor.visit_async_generator_mut(ag), + Self::Async(af) => visitor.visit_async_function_mut(af), } } } @@ -273,7 +269,7 @@ pub enum PropertyName { impl PropertyName { /// Returns the literal property name if it exists. #[must_use] - pub fn literal(&self) -> Option { + pub const fn literal(&self) -> Option { if let Self::Literal(sym) = self { Some(*sym) } else { @@ -283,7 +279,7 @@ impl PropertyName { /// Returns the expression if the property name is computed. #[must_use] - pub fn computed(&self) -> Option<&Expression> { + pub const fn computed(&self) -> Option<&Expression> { if let Self::Computed(expr) = self { Some(expr) } else { @@ -293,11 +289,12 @@ impl PropertyName { /// Returns either the literal property name or the computed const string property name. #[must_use] - pub fn prop_name(&self) -> Option { + pub const fn prop_name(&self) -> Option { match self { - PropertyName::Literal(sym) - | PropertyName::Computed(Expression::Literal(Literal::String(sym))) => Some(*sym), - PropertyName::Computed(_) => None, + Self::Literal(sym) | Self::Computed(Expression::Literal(Literal::String(sym))) => { + Some(*sym) + } + Self::Computed(_) => None, } } } @@ -305,8 +302,8 @@ impl PropertyName { impl ToInternedString for PropertyName { fn to_interned_string(&self, interner: &Interner) -> String { match self { - PropertyName::Literal(key) => interner.resolve_expect(*key).to_string(), - PropertyName::Computed(key) => key.to_interned_string(interner), + Self::Literal(key) => interner.resolve_expect(*key).to_string(), + Self::Computed(key) => key.to_interned_string(interner), } } } @@ -329,8 +326,8 @@ impl VisitWith for PropertyName { V: Visitor<'a>, { match self { - PropertyName::Literal(sym) => visitor.visit_sym(sym), - PropertyName::Computed(expr) => visitor.visit_expression(expr), + Self::Literal(sym) => visitor.visit_sym(sym), + Self::Computed(expr) => visitor.visit_expression(expr), } } @@ -339,8 +336,8 @@ impl VisitWith for PropertyName { V: VisitorMut<'a>, { match self { - PropertyName::Literal(sym) => visitor.visit_sym_mut(sym), - PropertyName::Computed(expr) => visitor.visit_expression_mut(expr), + Self::Literal(sym) => visitor.visit_sym_mut(sym), + Self::Computed(expr) => visitor.visit_expression_mut(expr), } } } diff --git a/boa_ast/src/statement/block.rs b/boa_ast/src/statement/block.rs index afd402ae33e..ca7f4ff548c 100644 --- a/boa_ast/src/statement/block.rs +++ b/boa_ast/src/statement/block.rs @@ -34,7 +34,7 @@ impl Block { /// Gets the list of statements and declarations in this block. #[inline] #[must_use] - pub fn statement_list(&self) -> &StatementList { + pub const fn statement_list(&self) -> &StatementList { &self.statements } } diff --git a/boa_ast/src/statement/if.rs b/boa_ast/src/statement/if.rs index f41cbae6614..0a9445ac74b 100644 --- a/boa_ast/src/statement/if.rs +++ b/boa_ast/src/statement/if.rs @@ -38,14 +38,14 @@ impl If { /// Gets the condition of the if statement. #[inline] #[must_use] - pub fn cond(&self) -> &Expression { + pub const fn cond(&self) -> &Expression { &self.condition } /// Gets the body to execute if the condition is true. #[inline] #[must_use] - pub fn body(&self) -> &Statement { + pub const fn body(&self) -> &Statement { &self.body } diff --git a/boa_ast/src/statement/iteration/break.rs b/boa_ast/src/statement/iteration/break.rs index 578f91a9027..f1f5f821c0a 100644 --- a/boa_ast/src/statement/iteration/break.rs +++ b/boa_ast/src/statement/iteration/break.rs @@ -28,24 +28,23 @@ pub struct Break { impl Break { /// Creates a `Break` AST node. #[must_use] - pub fn new(label: Option) -> Self { + pub const fn new(label: Option) -> Self { Self { label } } /// Gets the label of the break statement, if any. #[must_use] - pub fn label(&self) -> Option { + pub const fn label(&self) -> Option { self.label } } impl ToInternedString for Break { fn to_interned_string(&self, interner: &Interner) -> String { - if let Some(label) = self.label { - format!("break {}", interner.resolve_expect(label)) - } else { - "break".to_owned() - } + self.label.map_or_else( + || "break".to_owned(), + |label| format!("break {}", interner.resolve_expect(label)), + ) } } diff --git a/boa_ast/src/statement/iteration/continue.rs b/boa_ast/src/statement/iteration/continue.rs index ed2a19f527e..ed5ddb2f2c0 100644 --- a/boa_ast/src/statement/iteration/continue.rs +++ b/boa_ast/src/statement/iteration/continue.rs @@ -26,24 +26,23 @@ pub struct Continue { impl Continue { /// Creates a `Continue` AST node. #[must_use] - pub fn new(label: Option) -> Self { + pub const fn new(label: Option) -> Self { Self { label } } /// Gets the label of this `Continue` statement. #[must_use] - pub fn label(&self) -> Option { + pub const fn label(&self) -> Option { self.label } } impl ToInternedString for Continue { fn to_interned_string(&self, interner: &Interner) -> String { - if let Some(label) = self.label { - format!("continue {}", interner.resolve_expect(label)) - } else { - "continue".to_owned() - } + self.label.map_or_else( + || "continue".to_owned(), + |label| format!("continue {}", interner.resolve_expect(label)), + ) } } diff --git a/boa_ast/src/statement/iteration/do_while_loop.rs b/boa_ast/src/statement/iteration/do_while_loop.rs index 34fd03f33a4..f3e0b3062b7 100644 --- a/boa_ast/src/statement/iteration/do_while_loop.rs +++ b/boa_ast/src/statement/iteration/do_while_loop.rs @@ -31,14 +31,14 @@ impl DoWhileLoop { /// Gets the body of the do-while loop. #[inline] #[must_use] - pub fn body(&self) -> &Statement { + pub const fn body(&self) -> &Statement { &self.body } /// Gets the condition of the do-while loop. #[inline] #[must_use] - pub fn cond(&self) -> &Expression { + pub const fn cond(&self) -> &Expression { &self.condition } /// Creates a `DoWhileLoop` AST node. diff --git a/boa_ast/src/statement/iteration/for_in_loop.rs b/boa_ast/src/statement/iteration/for_in_loop.rs index 41e11083eff..9c2774accee 100644 --- a/boa_ast/src/statement/iteration/for_in_loop.rs +++ b/boa_ast/src/statement/iteration/for_in_loop.rs @@ -38,21 +38,21 @@ impl ForInLoop { /// Gets the initializer of the for...in loop. #[inline] #[must_use] - pub fn initializer(&self) -> &IterableLoopInitializer { + pub const fn initializer(&self) -> &IterableLoopInitializer { &self.initializer } /// Gets the target object of the for...in loop. #[inline] #[must_use] - pub fn target(&self) -> &Expression { + pub const fn target(&self) -> &Expression { &self.target } /// Gets the body of the for...in loop. #[inline] #[must_use] - pub fn body(&self) -> &Statement { + pub const fn body(&self) -> &Statement { &self.body } } diff --git a/boa_ast/src/statement/iteration/for_loop.rs b/boa_ast/src/statement/iteration/for_loop.rs index ea661100447..d0c79dc09a2 100644 --- a/boa_ast/src/statement/iteration/for_loop.rs +++ b/boa_ast/src/statement/iteration/for_loop.rs @@ -44,28 +44,28 @@ impl ForLoop { /// Gets the initialization node. #[inline] #[must_use] - pub fn init(&self) -> Option<&ForLoopInitializer> { + pub const fn init(&self) -> Option<&ForLoopInitializer> { self.inner.init() } /// Gets the loop condition node. #[inline] #[must_use] - pub fn condition(&self) -> Option<&Expression> { + pub const fn condition(&self) -> Option<&Expression> { self.inner.condition() } /// Gets the final expression node. #[inline] #[must_use] - pub fn final_expr(&self) -> Option<&Expression> { + pub const fn final_expr(&self) -> Option<&Expression> { self.inner.final_expr() } /// Gets the body of the for loop. #[inline] #[must_use] - pub fn body(&self) -> &Statement { + pub const fn body(&self) -> &Statement { self.inner.body() } } @@ -148,7 +148,7 @@ struct InnerForLoop { impl InnerForLoop { /// Creates a new inner for loop. #[inline] - fn new( + const fn new( init: Option, condition: Option, final_expr: Option, @@ -164,25 +164,25 @@ impl InnerForLoop { /// Gets the initialization node. #[inline] - fn init(&self) -> Option<&ForLoopInitializer> { + const fn init(&self) -> Option<&ForLoopInitializer> { self.init.as_ref() } /// Gets the loop condition node. #[inline] - fn condition(&self) -> Option<&Expression> { + const fn condition(&self) -> Option<&Expression> { self.condition.as_ref() } /// Gets the final expression node. #[inline] - fn final_expr(&self) -> Option<&Expression> { + const fn final_expr(&self) -> Option<&Expression> { self.final_expr.as_ref() } /// Gets the body of the for loop. #[inline] - fn body(&self) -> &Statement { + const fn body(&self) -> &Statement { &self.body } } @@ -220,21 +220,21 @@ impl ToInternedString for ForLoopInitializer { impl From for ForLoopInitializer { #[inline] fn from(expr: Expression) -> Self { - ForLoopInitializer::Expression(expr) + Self::Expression(expr) } } impl From for ForLoopInitializer { #[inline] fn from(list: LexicalDeclaration) -> Self { - ForLoopInitializer::Lexical(list) + Self::Lexical(list) } } impl From for ForLoopInitializer { #[inline] fn from(list: VarDeclaration) -> Self { - ForLoopInitializer::Var(list) + Self::Var(list) } } @@ -244,9 +244,9 @@ impl VisitWith for ForLoopInitializer { V: Visitor<'a>, { match self { - ForLoopInitializer::Expression(expr) => visitor.visit_expression(expr), - ForLoopInitializer::Var(vd) => visitor.visit_var_declaration(vd), - ForLoopInitializer::Lexical(ld) => visitor.visit_lexical_declaration(ld), + Self::Expression(expr) => visitor.visit_expression(expr), + Self::Var(vd) => visitor.visit_var_declaration(vd), + Self::Lexical(ld) => visitor.visit_lexical_declaration(ld), } } @@ -255,9 +255,9 @@ impl VisitWith for ForLoopInitializer { V: VisitorMut<'a>, { match self { - ForLoopInitializer::Expression(expr) => visitor.visit_expression_mut(expr), - ForLoopInitializer::Var(vd) => visitor.visit_var_declaration_mut(vd), - ForLoopInitializer::Lexical(ld) => visitor.visit_lexical_declaration_mut(ld), + Self::Expression(expr) => visitor.visit_expression_mut(expr), + Self::Var(vd) => visitor.visit_var_declaration_mut(vd), + Self::Lexical(ld) => visitor.visit_lexical_declaration_mut(ld), } } } diff --git a/boa_ast/src/statement/iteration/for_of_loop.rs b/boa_ast/src/statement/iteration/for_of_loop.rs index ead1768106c..d5b02aa4975 100644 --- a/boa_ast/src/statement/iteration/for_of_loop.rs +++ b/boa_ast/src/statement/iteration/for_of_loop.rs @@ -50,28 +50,28 @@ impl ForOfLoop { /// Gets the initializer of the for...of loop. #[inline] #[must_use] - pub fn initializer(&self) -> &IterableLoopInitializer { + pub const fn initializer(&self) -> &IterableLoopInitializer { &self.init } /// Gets the iterable expression of the for...of loop. #[inline] #[must_use] - pub fn iterable(&self) -> &Expression { + pub const fn iterable(&self) -> &Expression { &self.iterable } /// Gets the body to execute in the for...of loop. #[inline] #[must_use] - pub fn body(&self) -> &Statement { + pub const fn body(&self) -> &Statement { &self.body } /// Returns true if this "for...of" loop is an "for await...of" loop. #[inline] #[must_use] - pub fn r#await(&self) -> bool { + pub const fn r#await(&self) -> bool { self.r#await } } diff --git a/boa_ast/src/statement/iteration/mod.rs b/boa_ast/src/statement/iteration/mod.rs index 4377bedc694..de7ebc7c87d 100644 --- a/boa_ast/src/statement/iteration/mod.rs +++ b/boa_ast/src/statement/iteration/mod.rs @@ -73,12 +73,10 @@ impl VisitWith for IterableLoopInitializer { V: Visitor<'a>, { match self { - IterableLoopInitializer::Identifier(id) => visitor.visit_identifier(id), - IterableLoopInitializer::Access(pa) => visitor.visit_property_access(pa), - IterableLoopInitializer::Var(b) - | IterableLoopInitializer::Let(b) - | IterableLoopInitializer::Const(b) => visitor.visit_binding(b), - IterableLoopInitializer::Pattern(p) => visitor.visit_pattern(p), + Self::Identifier(id) => visitor.visit_identifier(id), + Self::Access(pa) => visitor.visit_property_access(pa), + Self::Var(b) | Self::Let(b) | Self::Const(b) => visitor.visit_binding(b), + Self::Pattern(p) => visitor.visit_pattern(p), } } @@ -87,12 +85,10 @@ impl VisitWith for IterableLoopInitializer { V: VisitorMut<'a>, { match self { - IterableLoopInitializer::Identifier(id) => visitor.visit_identifier_mut(id), - IterableLoopInitializer::Access(pa) => visitor.visit_property_access_mut(pa), - IterableLoopInitializer::Var(b) - | IterableLoopInitializer::Let(b) - | IterableLoopInitializer::Const(b) => visitor.visit_binding_mut(b), - IterableLoopInitializer::Pattern(p) => visitor.visit_pattern_mut(p), + Self::Identifier(id) => visitor.visit_identifier_mut(id), + Self::Access(pa) => visitor.visit_property_access_mut(pa), + Self::Var(b) | Self::Let(b) | Self::Const(b) => visitor.visit_binding_mut(b), + Self::Pattern(p) => visitor.visit_pattern_mut(p), } } } diff --git a/boa_ast/src/statement/iteration/while_loop.rs b/boa_ast/src/statement/iteration/while_loop.rs index ab568182100..00494d66ae8 100644 --- a/boa_ast/src/statement/iteration/while_loop.rs +++ b/boa_ast/src/statement/iteration/while_loop.rs @@ -40,14 +40,14 @@ impl WhileLoop { /// Gets the condition of the while loop. #[inline] #[must_use] - pub fn condition(&self) -> &Expression { + pub const fn condition(&self) -> &Expression { &self.condition } /// Gets the body of the while loop. #[inline] #[must_use] - pub fn body(&self) -> &Statement { + pub const fn body(&self) -> &Statement { &self.body } } diff --git a/boa_ast/src/statement/labelled.rs b/boa_ast/src/statement/labelled.rs index 89884ff8537..1fb5cdd3c4e 100644 --- a/boa_ast/src/statement/labelled.rs +++ b/boa_ast/src/statement/labelled.rs @@ -29,8 +29,8 @@ pub enum LabelledItem { impl LabelledItem { pub(crate) fn to_indented_string(&self, interner: &Interner, indentation: usize) -> String { match self { - LabelledItem::Function(f) => f.to_indented_string(interner, indentation), - LabelledItem::Statement(stmt) => stmt.to_indented_string(interner, indentation), + Self::Function(f) => f.to_indented_string(interner, indentation), + Self::Statement(stmt) => stmt.to_indented_string(interner, indentation), } } } @@ -59,8 +59,8 @@ impl VisitWith for LabelledItem { V: Visitor<'a>, { match self { - LabelledItem::Function(f) => visitor.visit_function(f), - LabelledItem::Statement(s) => visitor.visit_statement(s), + Self::Function(f) => visitor.visit_function(f), + Self::Statement(s) => visitor.visit_statement(s), } } @@ -69,8 +69,8 @@ impl VisitWith for LabelledItem { V: VisitorMut<'a>, { match self { - LabelledItem::Function(f) => visitor.visit_function_mut(f), - LabelledItem::Statement(s) => visitor.visit_statement_mut(s), + Self::Function(f) => visitor.visit_function_mut(f), + Self::Statement(s) => visitor.visit_statement_mut(s), } } } @@ -101,13 +101,13 @@ impl Labelled { /// Gets the labelled item. #[must_use] - pub fn item(&self) -> &LabelledItem { + pub const fn item(&self) -> &LabelledItem { &self.item } /// Gets the label name. #[must_use] - pub fn label(&self) -> Sym { + pub const fn label(&self) -> Sym { self.label } diff --git a/boa_ast/src/statement/mod.rs b/boa_ast/src/statement/mod.rs index f412a47b98b..bbd610b190a 100644 --- a/boa_ast/src/statement/mod.rs +++ b/boa_ast/src/statement/mod.rs @@ -188,26 +188,26 @@ impl VisitWith for Statement { V: Visitor<'a>, { match self { - Statement::Block(b) => visitor.visit_block(b), - Statement::Var(v) => visitor.visit_var_declaration(v), - Statement::Empty => { + Self::Block(b) => visitor.visit_block(b), + Self::Var(v) => visitor.visit_var_declaration(v), + Self::Empty => { // do nothing; there is nothing to visit here ControlFlow::Continue(()) } - Statement::Expression(e) => visitor.visit_expression(e), - Statement::If(i) => visitor.visit_if(i), - Statement::DoWhileLoop(dw) => visitor.visit_do_while_loop(dw), - Statement::WhileLoop(w) => visitor.visit_while_loop(w), - Statement::ForLoop(f) => visitor.visit_for_loop(f), - Statement::ForInLoop(fi) => visitor.visit_for_in_loop(fi), - Statement::ForOfLoop(fo) => visitor.visit_for_of_loop(fo), - Statement::Switch(s) => visitor.visit_switch(s), - Statement::Continue(c) => visitor.visit_continue(c), - Statement::Break(b) => visitor.visit_break(b), - Statement::Return(r) => visitor.visit_return(r), - Statement::Labelled(l) => visitor.visit_labelled(l), - Statement::Throw(th) => visitor.visit_throw(th), - Statement::Try(tr) => visitor.visit_try(tr), + Self::Expression(e) => visitor.visit_expression(e), + Self::If(i) => visitor.visit_if(i), + Self::DoWhileLoop(dw) => visitor.visit_do_while_loop(dw), + Self::WhileLoop(w) => visitor.visit_while_loop(w), + Self::ForLoop(f) => visitor.visit_for_loop(f), + Self::ForInLoop(fi) => visitor.visit_for_in_loop(fi), + Self::ForOfLoop(fo) => visitor.visit_for_of_loop(fo), + Self::Switch(s) => visitor.visit_switch(s), + Self::Continue(c) => visitor.visit_continue(c), + Self::Break(b) => visitor.visit_break(b), + Self::Return(r) => visitor.visit_return(r), + Self::Labelled(l) => visitor.visit_labelled(l), + Self::Throw(th) => visitor.visit_throw(th), + Self::Try(tr) => visitor.visit_try(tr), } } @@ -216,26 +216,26 @@ impl VisitWith for Statement { V: VisitorMut<'a>, { match self { - Statement::Block(b) => visitor.visit_block_mut(b), - Statement::Var(v) => visitor.visit_var_declaration_mut(v), - Statement::Empty => { + Self::Block(b) => visitor.visit_block_mut(b), + Self::Var(v) => visitor.visit_var_declaration_mut(v), + Self::Empty => { // do nothing; there is nothing to visit here ControlFlow::Continue(()) } - Statement::Expression(e) => visitor.visit_expression_mut(e), - Statement::If(i) => visitor.visit_if_mut(i), - Statement::DoWhileLoop(dw) => visitor.visit_do_while_loop_mut(dw), - Statement::WhileLoop(w) => visitor.visit_while_loop_mut(w), - Statement::ForLoop(f) => visitor.visit_for_loop_mut(f), - Statement::ForInLoop(fi) => visitor.visit_for_in_loop_mut(fi), - Statement::ForOfLoop(fo) => visitor.visit_for_of_loop_mut(fo), - Statement::Switch(s) => visitor.visit_switch_mut(s), - Statement::Continue(c) => visitor.visit_continue_mut(c), - Statement::Break(b) => visitor.visit_break_mut(b), - Statement::Return(r) => visitor.visit_return_mut(r), - Statement::Labelled(l) => visitor.visit_labelled_mut(l), - Statement::Throw(th) => visitor.visit_throw_mut(th), - Statement::Try(tr) => visitor.visit_try_mut(tr), + Self::Expression(e) => visitor.visit_expression_mut(e), + Self::If(i) => visitor.visit_if_mut(i), + Self::DoWhileLoop(dw) => visitor.visit_do_while_loop_mut(dw), + Self::WhileLoop(w) => visitor.visit_while_loop_mut(w), + Self::ForLoop(f) => visitor.visit_for_loop_mut(f), + Self::ForInLoop(fi) => visitor.visit_for_in_loop_mut(fi), + Self::ForOfLoop(fo) => visitor.visit_for_of_loop_mut(fo), + Self::Switch(s) => visitor.visit_switch_mut(s), + Self::Continue(c) => visitor.visit_continue_mut(c), + Self::Break(b) => visitor.visit_break_mut(b), + Self::Return(r) => visitor.visit_return_mut(r), + Self::Labelled(l) => visitor.visit_labelled_mut(l), + Self::Throw(th) => visitor.visit_throw_mut(th), + Self::Try(tr) => visitor.visit_try_mut(tr), } } } diff --git a/boa_ast/src/statement/return.rs b/boa_ast/src/statement/return.rs index 6cd3d36ba22..9477a2805bf 100644 --- a/boa_ast/src/statement/return.rs +++ b/boa_ast/src/statement/return.rs @@ -33,13 +33,13 @@ pub struct Return { impl Return { /// Gets the target expression value of this `Return` statement. #[must_use] - pub fn target(&self) -> Option<&Expression> { + pub const fn target(&self) -> Option<&Expression> { self.target.as_ref() } /// Creates a `Return` AST node. #[must_use] - pub fn new(expression: Option) -> Self { + pub const fn new(expression: Option) -> Self { Self { target: expression } } } @@ -52,10 +52,10 @@ impl From for Statement { impl ToInternedString for Return { fn to_interned_string(&self, interner: &Interner) -> String { - match self.target() { - Some(ex) => format!("return {}", ex.to_interned_string(interner)), - None => "return".to_owned(), - } + self.target().map_or_else( + || "return".to_owned(), + |ex| format!("return {}", ex.to_interned_string(interner)), + ) } } diff --git a/boa_ast/src/statement/switch.rs b/boa_ast/src/statement/switch.rs index 77ce5d79442..6da9f8e2c14 100644 --- a/boa_ast/src/statement/switch.rs +++ b/boa_ast/src/statement/switch.rs @@ -30,21 +30,21 @@ impl Case { /// Creates a `Case` AST node. #[inline] #[must_use] - pub fn new(condition: Expression, body: StatementList) -> Self { + pub const fn new(condition: Expression, body: StatementList) -> Self { Self { condition, body } } /// Gets the condition of the case. #[inline] #[must_use] - pub fn condition(&self) -> &Expression { + pub const fn condition(&self) -> &Expression { &self.condition } /// Gets the statement listin the body of the case. #[inline] #[must_use] - pub fn body(&self) -> &StatementList { + pub const fn body(&self) -> &StatementList { &self.body } } @@ -107,21 +107,21 @@ impl Switch { /// Gets the value to switch. #[inline] #[must_use] - pub fn val(&self) -> &Expression { + pub const fn val(&self) -> &Expression { &self.val } /// Gets the list of cases for the switch statement. #[inline] #[must_use] - pub fn cases(&self) -> &[Case] { + pub const fn cases(&self) -> &[Case] { &self.cases } /// Gets the default statement list, if any. #[inline] #[must_use] - pub fn default(&self) -> Option<&StatementList> { + pub const fn default(&self) -> Option<&StatementList> { self.default.as_ref() } } diff --git a/boa_ast/src/statement/throw.rs b/boa_ast/src/statement/throw.rs index e3936d5c919..90df3b04ad3 100644 --- a/boa_ast/src/statement/throw.rs +++ b/boa_ast/src/statement/throw.rs @@ -30,13 +30,13 @@ pub struct Throw { impl Throw { /// Gets the target expression of this `Throw` statement. #[must_use] - pub fn target(&self) -> &Expression { + pub const fn target(&self) -> &Expression { &self.target } /// Creates a `Throw` AST node. #[must_use] - pub fn new(target: Expression) -> Self { + pub const fn new(target: Expression) -> Self { Self { target } } } diff --git a/boa_ast/src/statement/try.rs b/boa_ast/src/statement/try.rs index c8c673e69a1..2e5a67bc87e 100644 --- a/boa_ast/src/statement/try.rs +++ b/boa_ast/src/statement/try.rs @@ -47,21 +47,21 @@ impl Try { /// Creates a new `Try` AST node. #[inline] #[must_use] - pub fn new(block: Block, handler: ErrorHandler) -> Self { + pub const fn new(block: Block, handler: ErrorHandler) -> Self { Self { block, handler } } /// Gets the `try` block. #[inline] #[must_use] - pub fn block(&self) -> &Block { + pub const fn block(&self) -> &Block { &self.block } /// Gets the `catch` block, if any. #[inline] #[must_use] - pub fn catch(&self) -> Option<&Catch> { + pub const fn catch(&self) -> Option<&Catch> { match &self.handler { ErrorHandler::Catch(c) | ErrorHandler::Full(c, _) => Some(c), ErrorHandler::Finally(_) => None, @@ -71,7 +71,7 @@ impl Try { /// Gets the `finally` block, if any. #[inline] #[must_use] - pub fn finally(&self) -> Option<&Finally> { + pub const fn finally(&self) -> Option<&Finally> { match &self.handler { ErrorHandler::Finally(f) | ErrorHandler::Full(_, f) => Some(f), ErrorHandler::Catch(_) => None, @@ -150,21 +150,21 @@ impl Catch { /// Creates a new catch block. #[inline] #[must_use] - pub fn new(parameter: Option, block: Block) -> Self { + pub const fn new(parameter: Option, block: Block) -> Self { Self { parameter, block } } /// Gets the parameter of the catch block. #[inline] #[must_use] - pub fn parameter(&self) -> Option<&Binding> { + pub const fn parameter(&self) -> Option<&Binding> { self.parameter.as_ref() } /// Retrieves the catch execution block. #[inline] #[must_use] - pub fn block(&self) -> &Block { + pub const fn block(&self) -> &Block { &self.block } } @@ -218,7 +218,7 @@ impl Finally { /// Gets the finally block. #[inline] #[must_use] - pub fn block(&self) -> &Block { + pub const fn block(&self) -> &Block { &self.block } } diff --git a/boa_ast/src/statement_list.rs b/boa_ast/src/statement_list.rs index 35221ecf070..1e6cacf6da3 100644 --- a/boa_ast/src/statement_list.rs +++ b/boa_ast/src/statement_list.rs @@ -30,7 +30,7 @@ pub enum StatementListItem { impl StatementListItem { /// Returns a node ordering based on the hoistability of each statement. #[must_use] - pub fn hoistable_order(a: &Self, b: &Self) -> Ordering { + pub const fn hoistable_order(a: &Self, b: &Self) -> Ordering { match (a, b) { ( Self::Declaration(Declaration::Function(_)), @@ -59,10 +59,10 @@ impl ToIndentedString for StatementListItem { let mut buf = " ".repeat(indentation); match self { - StatementListItem::Statement(stmt) => { + Self::Statement(stmt) => { buf.push_str(&stmt.to_no_indent_string(interner, indentation)); } - StatementListItem::Declaration(decl) => { + Self::Declaration(decl) => { buf.push_str(&decl.to_indented_string(interner, indentation)); } } @@ -74,14 +74,14 @@ impl ToIndentedString for StatementListItem { impl From for StatementListItem { #[inline] fn from(stmt: Statement) -> Self { - StatementListItem::Statement(stmt) + Self::Statement(stmt) } } impl From for StatementListItem { #[inline] fn from(decl: Declaration) -> Self { - StatementListItem::Declaration(decl) + Self::Declaration(decl) } } @@ -91,8 +91,8 @@ impl VisitWith for StatementListItem { V: Visitor<'a>, { match self { - StatementListItem::Statement(statement) => visitor.visit_statement(statement), - StatementListItem::Declaration(declaration) => visitor.visit_declaration(declaration), + Self::Statement(statement) => visitor.visit_statement(statement), + Self::Declaration(declaration) => visitor.visit_declaration(declaration), } } @@ -101,10 +101,8 @@ impl VisitWith for StatementListItem { V: VisitorMut<'a>, { match self { - StatementListItem::Statement(statement) => visitor.visit_statement_mut(statement), - StatementListItem::Declaration(declaration) => { - visitor.visit_declaration_mut(declaration) - } + Self::Statement(statement) => visitor.visit_statement_mut(statement), + Self::Declaration(declaration) => visitor.visit_declaration_mut(declaration), } } } @@ -126,14 +124,14 @@ impl StatementList { /// Gets the list of statements. #[inline] #[must_use] - pub fn statements(&self) -> &[StatementListItem] { + pub const fn statements(&self) -> &[StatementListItem] { &self.statements } /// Get the strict mode. #[inline] #[must_use] - pub fn strict(&self) -> bool { + pub const fn strict(&self) -> bool { self.strict } diff --git a/boa_cli/Cargo.toml b/boa_cli/Cargo.toml index c19dc7772fc..97966ca1118 100644 --- a/boa_cli/Cargo.toml +++ b/boa_cli/Cargo.toml @@ -14,12 +14,11 @@ rust-version.workspace = true [dependencies] boa_engine = { workspace = true, features = ["deser", "console", "flowgraph"] } boa_ast = { workspace = true, features = ["serde"]} -boa_interner.workspace = true boa_parser.workspace = true rustyline = "10.0.0" rustyline-derive = "0.7.0" -clap = { version = "4.0.22", features = ["derive"] } -serde_json = "1.0.87" +clap = { version = "4.0.26", features = ["derive"] } +serde_json = "1.0.89" colored = "2.0.0" regex = "1.7.0" phf = { version = "0.11.1", features = ["macros"] } diff --git a/boa_cli/src/main.rs b/boa_cli/src/main.rs index fc257cbb0b1..1185de1f166 100644 --- a/boa_cli/src/main.rs +++ b/boa_cli/src/main.rs @@ -1,63 +1,64 @@ +//! A ECMAScript REPL implementation based on boa_engine. + #![doc( html_logo_url = "https://raw.githubusercontent.com/boa-dev/boa/main/assets/logo.svg", html_favicon_url = "https://raw.githubusercontent.com/boa-dev/boa/main/assets/logo.svg" )] #![cfg_attr(not(test), deny(clippy::unwrap_used))] -#![warn( - clippy::perf, - clippy::single_match_else, - clippy::dbg_macro, - clippy::doc_markdown, - clippy::wildcard_imports, - clippy::struct_excessive_bools, - clippy::doc_markdown, - clippy::semicolon_if_nothing_returned, - clippy::pedantic -)] +#![warn(missing_docs, clippy::dbg_macro)] #![deny( - clippy::all, - clippy::cast_lossless, - clippy::redundant_closure_for_method_calls, - clippy::use_self, - clippy::unnested_or_patterns, - clippy::trivially_copy_pass_by_ref, - clippy::needless_pass_by_value, - clippy::match_wildcard_for_single_variants, - clippy::map_unwrap_or, - unused_qualifications, - unused_import_braces, - unused_lifetimes, - unreachable_pub, - trivial_numeric_casts, - // rustdoc, - missing_debug_implementations, - missing_copy_implementations, - deprecated_in_future, - meta_variable_misuse, - non_ascii_idents, - rust_2018_compatibility, - rust_2018_idioms, + // rustc lint groups https://doc.rust-lang.org/rustc/lints/groups.html + warnings, future_incompatible, + let_underscore, nonstandard_style, + rust_2018_compatibility, + rust_2018_idioms, + rust_2021_compatibility, + unused, + + // rustc allowed-by-default lints https://doc.rust-lang.org/rustc/lints/listing/allowed-by-default.html + macro_use_extern_crate, + meta_variable_misuse, + missing_abi, + missing_copy_implementations, + missing_debug_implementations, + non_ascii_idents, + noop_method_call, + single_use_lifetimes, + trivial_casts, + trivial_numeric_casts, + unreachable_pub, + unsafe_op_in_unsafe_fn, + unused_crate_dependencies, + unused_import_braces, + unused_lifetimes, + unused_qualifications, + unused_tuple_struct_fields, + variant_size_differences, + + // rustdoc lints https://doc.rust-lang.org/rustdoc/lints.html + rustdoc::broken_intra_doc_links, + rustdoc::private_intra_doc_links, + rustdoc::missing_crate_level_docs, + rustdoc::private_doc_tests, + rustdoc::invalid_codeblock_attributes, + rustdoc::invalid_rust_codeblocks, + rustdoc::bare_urls, + + // clippy categories https://doc.rust-lang.org/clippy/ + clippy::all, + clippy::correctness, + clippy::suspicious, + clippy::style, + clippy::complexity, + clippy::perf, + clippy::pedantic, + clippy::nursery, )] -#![allow( - clippy::module_name_repetitions, - clippy::cast_possible_truncation, - clippy::cast_sign_loss, - clippy::cast_precision_loss, - clippy::cast_possible_wrap, - clippy::cast_ptr_alignment, - clippy::missing_panics_doc, - clippy::too_many_lines, - clippy::unreadable_literal, - clippy::missing_inline_in_public_items, - clippy::cognitive_complexity, - clippy::must_use_candidate, - clippy::missing_errors_doc, - clippy::as_conversions, - clippy::let_unit_value, - rustdoc::missing_doc_code_examples -)] +#![allow(clippy::option_if_let_else, clippy::redundant_pub_crate)] + +mod helper; use boa_ast::StatementList; use boa_engine::{ @@ -68,7 +69,6 @@ use clap::{Parser, ValueEnum, ValueHint}; use colored::{Color, Colorize}; use rustyline::{config::Config, error::ReadlineError, EditMode, Editor}; use std::{fs::read, fs::OpenOptions, io, path::PathBuf}; -mod helper; #[cfg(all(target_arch = "x86_64", target_os = "linux", target_env = "gnu"))] #[cfg_attr( @@ -85,7 +85,6 @@ const READLINE_COLOR: Color = Color::Cyan; // Added #[allow(clippy::option_option)] because to StructOpt an Option> // is an optional argument that optionally takes a value ([--opt=[val]]). // https://docs.rs/structopt/0.3.11/structopt/#type-magic -#[allow(clippy::option_option)] #[derive(Debug, Parser)] #[command(author, version, about, name = "boa")] struct Opt { @@ -102,6 +101,7 @@ struct Opt { value_enum, conflicts_with = "graph" )] + #[allow(clippy::option_option)] dump_ast: Option>, /// Dump the AST to stdout with the given format. @@ -135,7 +135,7 @@ struct Opt { impl Opt { /// Returns whether a dump flag has been used. - fn has_dump_flag(&self) -> bool { + const fn has_dump_flag(&self) -> bool { self.dump_ast.is_some() } } @@ -208,22 +208,16 @@ where let ast = parse_tokens(src, context)?; match arg { - Some(format) => match format { - DumpFormat::Debug => println!("{ast:#?}"), - DumpFormat::Json => println!( - "{}", - serde_json::to_string(&ast).expect("could not convert AST to a JSON string") - ), - DumpFormat::JsonPretty => { - println!( - "{}", - serde_json::to_string_pretty(&ast) - .expect("could not convert AST to a pretty JSON string") - ); - } - }, - // Default ast dumping format. - None => println!("{ast:#?}"), + Some(DumpFormat::Json) => println!( + "{}", + serde_json::to_string(&ast).expect("could not convert AST to a JSON string") + ), + Some(DumpFormat::JsonPretty) => println!( + "{}", + serde_json::to_string_pretty(&ast) + .expect("could not convert AST to a pretty JSON string") + ), + Some(DumpFormat::Debug) | None => println!("{ast:#?}"), } } @@ -255,7 +249,7 @@ fn generate_flowgraph( Ok(result) } -pub fn main() -> Result<(), io::Error> { +fn main() -> Result<(), io::Error> { let args = Opt::parse(); let mut context = Context::default(); diff --git a/boa_engine/Cargo.toml b/boa_engine/Cargo.toml index cc1e9532717..4230391da54 100644 --- a/boa_engine/Cargo.toml +++ b/boa_engine/Cargo.toml @@ -40,9 +40,8 @@ boa_profiler.workspace = true boa_macros.workspace = true boa_ast.workspace = true boa_parser.workspace = true -gc = "0.4.1" serde = { version = "1.0.147", features = ["derive", "rc"] } -serde_json = "1.0.87" +serde_json = "1.0.89" rand = "0.8.5" num-traits = "0.2.15" regress = "0.4.1" @@ -50,9 +49,9 @@ rustc-hash = "1.1.0" num-bigint = { version = "0.4.3", features = ["serde"] } num-integer = "0.1.45" bitflags = "1.3.2" -indexmap = "1.9.1" +indexmap = "1.9.2" ryu-js = "0.2.2" -chrono = "0.4.22" +chrono = "0.4.23" fast-float = "0.2.0" unicode-normalization = "0.1.22" dyn-clone = "1.0.9" diff --git a/boa_engine/src/builtins/array/mod.rs b/boa_engine/src/builtins/array/mod.rs index 846cec01c95..2e4245a8eab 100644 --- a/boa_engine/src/builtins/array/mod.rs +++ b/boa_engine/src/builtins/array/mod.rs @@ -420,10 +420,7 @@ impl Array { JsValue::Object(o) if o.is_callable() => Some(o), _ => { return Err(JsNativeError::typ() - .with_message(format!( - "{} is not a function", - mapfn.type_of().to_std_string_escaped() - )) + .with_message(format!("`{}` is not callable", mapfn.type_of())) .into()) } }; diff --git a/boa_engine/src/builtins/async_generator/mod.rs b/boa_engine/src/builtins/async_generator/mod.rs index e6ea7180641..768dfd48a62 100644 --- a/boa_engine/src/builtins/async_generator/mod.rs +++ b/boa_engine/src/builtins/async_generator/mod.rs @@ -18,7 +18,7 @@ use crate::{ vm::GeneratorResumeKind, Context, JsError, JsResult, }; -use boa_gc::{Cell, Finalize, Gc, Trace}; +use boa_gc::{Finalize, Gc, GcCell, Trace}; use boa_profiler::Profiler; use std::collections::VecDeque; @@ -56,7 +56,7 @@ pub struct AsyncGenerator { pub(crate) state: AsyncGeneratorState, /// The `[[AsyncGeneratorContext]]` internal slot. - pub(crate) context: Option>>, + pub(crate) context: Option>>, /// The `[[AsyncGeneratorQueue]]` internal slot. pub(crate) queue: VecDeque, @@ -511,7 +511,7 @@ impl AsyncGenerator { pub(crate) fn resume( generator: &JsObject, state: AsyncGeneratorState, - generator_context: &Gc>, + generator_context: &Gc>, completion: (JsResult, bool), context: &mut Context, ) { diff --git a/boa_engine/src/builtins/date/mod.rs b/boa_engine/src/builtins/date/mod.rs index b23877a7fc3..2319ce053be 100644 --- a/boa_engine/src/builtins/date/mod.rs +++ b/boa_engine/src/builtins/date/mod.rs @@ -12,7 +12,7 @@ use crate::{ }, string::utf16, symbol::WellKnownSymbols, - value::{JsValue, PreferredType}, + value::{IntegerOrInfinity, JsValue, PreferredType}, Context, JsError, JsResult, }; use boa_profiler::Profiler; @@ -121,9 +121,9 @@ impl BuiltIn for Date { .method(Self::to_gmt_string, "toGMTString", 0) .method(Self::to_iso_string, "toISOString", 0) .method(Self::to_json, "toJSON", 1) - .method(Self::to_locale_date_string, "toLocaleDateString", 2) - .method(Self::to_locale_string, "toLocaleString", 2) - .method(Self::to_locale_time_string, "toLocaleTimeString", 2) + .method(Self::to_locale_date_string, "toLocaleDateString", 0) + .method(Self::to_locale_string, "toLocaleString", 0) + .method(Self::to_locale_time_string, "toLocaleTimeString", 0) .method(Self::to_string, "toString", 0) .method(Self::to_time_string, "toTimeString", 0) .method(Self::to_utc_string, "toUTCString", 0) @@ -152,11 +152,16 @@ impl Date { /// /// [spec]: https://tc39.es/ecma262/#sec-timeclip #[inline] - pub fn time_clip(time: f64) -> Option { - if time.abs() > 8.64e15 { - None - } else { - Some(time) + pub fn time_clip(time: f64) -> Option { + // 1. If time is not finite, return NaN. + // 2. If abs(ℝ(time)) > 8.64 × 1015, return NaN. + // 3. Return 𝔽(! ToIntegerOrInfinity(time)). + if time.is_nan() { + return None; + } + match IntegerOrInfinity::from(time) { + IntegerOrInfinity::Integer(i) if i.abs() <= 864i64 * 10i64.pow(13) => Some(i), + _ => None, } } @@ -1329,26 +1334,17 @@ impl Date { this_time_value(this)?; // 2. Let t be ? ToNumber(time). - let t = if let Some(t) = args.get(0) { - let t = t.to_number(context)?; - let seconds = (t / 1_000f64) as i64; - let nanoseconds = ((t % 1_000f64) * 1_000_000f64) as u32; - Self( - ignore_ambiguity(Local.timestamp_opt(seconds, nanoseconds)) - .map(|dt| dt.naive_utc()), - ) - } else { - Self(None) - }; + let t = args.get_or_undefined(0).to_number(context)?; // 3. Let v be TimeClip(t). - let v = Self::get_time(this, args, context)?; + let v_int = Self::time_clip(t); + let v = v_int.map(|t| Local.timestamp_millis(t).naive_utc()); // 4. Set the [[DateValue]] internal slot of this Date object to v. - this.set_data(ObjectData::date(t)); + this.set_data(ObjectData::date(Date(v))); // 5. Return v. - Ok(v) + Ok(v_int.map_or(JsValue::Rational(f64::NAN), JsValue::from)) } /// `Date.prototype.setUTCDate()` @@ -1728,7 +1724,7 @@ impl Date { _args: &[JsValue], context: &mut Context, ) -> JsResult { - Self::to_utc_string(this, &[JsValue::Null], context) + Self::to_utc_string(this, &[], context) } /// `Date.prototype.toISOString()` @@ -1903,13 +1899,13 @@ impl Date { pub fn to_utc_string( this: &JsValue, _args: &[JsValue], - context: &mut Context, + _context: &mut Context, ) -> JsResult { if let Some(t) = this_time_value(this)?.0 { let utc_string = t.format("%a, %d %b %Y %H:%M:%S GMT").to_string(); Ok(JsValue::new(utc_string)) } else { - Ok(context.construct_error("Invalid time value")) + Ok(js_string!("Invalid Date").into()) } } diff --git a/boa_engine/src/builtins/function/mod.rs b/boa_engine/src/builtins/function/mod.rs index 5cc70deec57..ebd927fd795 100644 --- a/boa_engine/src/builtins/function/mod.rs +++ b/boa_engine/src/builtins/function/mod.rs @@ -34,7 +34,7 @@ use boa_ast::{ operations::{bound_names, contains, lexically_declared_names, ContainsSymbol}, StatementList, }; -use boa_gc::{self, custom_trace, Finalize, Gc, Trace}; +use boa_gc::{self, custom_trace, Finalize, Gc, GcCell, Trace}; use boa_interner::Sym; use boa_parser::Parser; use boa_profiler::Profiler; @@ -178,7 +178,7 @@ unsafe impl Trace for ClassFieldDefinition { /// with `Any::downcast_ref` and `Any::downcast_mut` to recover the original /// type. #[derive(Clone, Debug, Trace, Finalize)] -pub struct Captures(Gc>>); +pub struct Captures(Gc>>); impl Captures { /// Creates a new capture context. @@ -186,7 +186,7 @@ impl Captures { where T: NativeObject, { - Self(Gc::new(boa_gc::Cell::new(Box::new(captures)))) + Self(Gc::new(GcCell::new(Box::new(captures)))) } /// Casts `Captures` to `Any` @@ -194,7 +194,7 @@ impl Captures { /// # Panics /// /// Panics if it's already borrowed as `&mut Any` - pub fn as_any(&self) -> boa_gc::Ref<'_, dyn Any> { + pub fn as_any(&self) -> boa_gc::GcCellRef<'_, dyn Any> { Ref::map(self.0.borrow(), |data| data.deref().as_any()) } @@ -203,7 +203,7 @@ impl Captures { /// # Panics /// /// Panics if it's already borrowed as `&mut Any` - pub fn as_mut_any(&self) -> boa_gc::RefMut<'_, Box, dyn Any> { + pub fn as_mut_any(&self) -> boa_gc::GcCellRefMut<'_, Box, dyn Any> { RefMut::map(self.0.borrow_mut(), |data| data.deref_mut().as_mut_any()) } } diff --git a/boa_engine/src/builtins/generator/mod.rs b/boa_engine/src/builtins/generator/mod.rs index 74eea06fc13..7599dd90742 100644 --- a/boa_engine/src/builtins/generator/mod.rs +++ b/boa_engine/src/builtins/generator/mod.rs @@ -20,7 +20,7 @@ use crate::{ vm::{CallFrame, GeneratorResumeKind, ReturnType}, Context, JsError, JsResult, }; -use boa_gc::{Cell, Finalize, Gc, Trace}; +use boa_gc::{Finalize, Gc, GcCell, Trace}; use boa_profiler::Profiler; /// Indicates the state of a generator. @@ -52,7 +52,7 @@ pub struct Generator { pub(crate) state: GeneratorState, /// The `[[GeneratorContext]]` internal slot. - pub(crate) context: Option>>, + pub(crate) context: Option>>, } impl BuiltIn for Generator { diff --git a/boa_engine/src/builtins/mod.rs b/boa_engine/src/builtins/mod.rs index d6aeda78608..3e7aa658d8f 100644 --- a/boa_engine/src/builtins/mod.rs +++ b/boa_engine/src/builtins/mod.rs @@ -33,6 +33,7 @@ pub mod symbol; pub mod typed_array; pub mod undefined; pub mod uri; +pub mod weak; #[cfg(feature = "console")] pub mod console; @@ -82,44 +83,42 @@ use crate::{ builtins::{ array_buffer::ArrayBuffer, async_generator::AsyncGenerator, async_generator_function::AsyncGeneratorFunction, generator::Generator, - generator_function::GeneratorFunction, typed_array::TypedArray, uri::Uri, + generator_function::GeneratorFunction, typed_array::TypedArray, uri::Uri, weak::WeakRef, }, property::{Attribute, PropertyDescriptor}, Context, JsValue, }; -/// Trait representing a global built-in object such as `Math`, `Object` or -/// `String`. +/// Trait representing a global built-in object such as `Math`, `Object` or `String`. /// -/// This trait must be implemented for any global built-in accessible from -/// Javascript. +/// This trait must be implemented for any global built-in accessible from JavaScript. pub(crate) trait BuiltIn { /// Binding name of the built-in inside the global object. /// - /// E.g. If you want access the properties of a `Complex` built-in - /// with the name `Cplx` you must assign `"Cplx"` to this constant, - /// making any property inside it accessible from Javascript as `Cplx.prop` + /// E.g. If you want access the properties of a `Complex` built-in with the name `Cplx` you must + /// assign `"Cplx"` to this constant, making any property inside it accessible from Javascript + /// as `Cplx.prop` const NAME: &'static str; - /// Property attribute flags of the built-in. - /// Check [Attribute] for more information. + /// Property attribute flags of the built-in. Check [`Attribute`] for more information. const ATTRIBUTE: Attribute = Attribute::WRITABLE .union(Attribute::NON_ENUMERABLE) .union(Attribute::CONFIGURABLE); /// Initialization code for the built-in. - /// This is where the methods, properties, static methods and the constructor - /// of a built-in must be initialized to be accessible from Javascript. + /// + /// This is where the methods, properties, static methods and the constructor of a built-in must + /// be initialized to be accessible from Javascript. /// /// # Note /// - /// A return value of `None` indicates that the value must not be added as - /// a global attribute for the global object. + /// A return value of `None` indicates that the value must not be added as a global attribute + /// for the global object. fn init(context: &mut Context) -> Option; } -/// Utility function that checks if a type implements `BuiltIn` before -/// initializing it as a global built-in. +/// Utility function that checks if a type implements `BuiltIn` before initializing it as a global +/// built-in. #[inline] fn init_builtin(context: &mut Context) { if let Some(value) = B::init(context) { @@ -195,7 +194,8 @@ pub fn init(context: &mut Context) { AsyncFunction, AsyncGenerator, AsyncGeneratorFunction, - Uri + Uri, + WeakRef }; #[cfg(feature = "intl")] @@ -206,17 +206,15 @@ pub fn init(context: &mut Context) { } pub trait JsArgs { - /// Utility function to `get` a parameter from - /// a `[JsValue]` or default to `JsValue::Undefined` + /// Utility function to `get` a parameter from a `[JsValue]` or default to `JsValue::Undefined` /// if `get` returns `None`. /// /// Call this if you are thinking of calling something similar to /// `args.get(n).cloned().unwrap_or_default()` or /// `args.get(n).unwrap_or(&undefined)`. /// - /// This returns a reference for efficiency, in case - /// you only need to call methods of `JsValue`, so - /// try to minimize calling `clone`. + /// This returns a reference for efficiency, in case you only need to call methods of `JsValue`, + /// so try to minimize calling `clone`. fn get_or_undefined(&self, index: usize) -> &JsValue; } diff --git a/boa_engine/src/builtins/object/mod.rs b/boa_engine/src/builtins/object/mod.rs index a185a750d97..88859126f59 100644 --- a/boa_engine/src/builtins/object/mod.rs +++ b/boa_engine/src/builtins/object/mod.rs @@ -628,8 +628,8 @@ impl Object { val => { return Err(JsNativeError::typ() .with_message(format!( - "expected an object or null, got {}", - val.type_of().to_std_string_escaped() + "expected an object or null, got `{}`", + val.type_of() )) .into()) } diff --git a/boa_engine/src/builtins/promise/mod.rs b/boa_engine/src/builtins/promise/mod.rs index ae4ebef81e8..7bdf64cbc7a 100644 --- a/boa_engine/src/builtins/promise/mod.rs +++ b/boa_engine/src/builtins/promise/mod.rs @@ -21,7 +21,7 @@ use crate::{ value::JsValue, Context, JsError, JsResult, }; -use boa_gc::{Cell as GcCell, Finalize, Gc, Trace}; +use boa_gc::{Finalize, Gc, GcCell, Trace}; use boa_profiler::Profiler; use std::{cell::Cell, rc::Rc}; use tap::{Conv, Pipe}; @@ -118,7 +118,7 @@ impl PromiseCapability { // 2. NOTE: C is assumed to be a constructor function that supports the parameter conventions of the Promise constructor (see 27.2.3.1). // 3. Let promiseCapability be the PromiseCapability Record { [[Promise]]: undefined, [[Resolve]]: undefined, [[Reject]]: undefined }. - let promise_capability = Gc::new(boa_gc::Cell::new(RejectResolve { + let promise_capability = Gc::new(GcCell::new(RejectResolve { reject: JsValue::undefined(), resolve: JsValue::undefined(), })); diff --git a/boa_engine/src/builtins/weak/mod.rs b/boa_engine/src/builtins/weak/mod.rs new file mode 100644 index 00000000000..0beedf03846 --- /dev/null +++ b/boa_engine/src/builtins/weak/mod.rs @@ -0,0 +1,3 @@ +mod weak_ref; + +pub(crate) use weak_ref::WeakRef; diff --git a/boa_engine/src/builtins/weak/weak_ref.rs b/boa_engine/src/builtins/weak/weak_ref.rs new file mode 100644 index 00000000000..a111b5cd0fb --- /dev/null +++ b/boa_engine/src/builtins/weak/weak_ref.rs @@ -0,0 +1,166 @@ +use boa_gc::{Finalize, Trace, WeakGc}; +use boa_profiler::Profiler; +use tap::{Conv, Pipe}; + +use crate::{ + builtins::{BuiltIn, JsArgs}, + context::intrinsics::StandardConstructors, + object::{ + internal_methods::get_prototype_from_constructor, ConstructorBuilder, JsObject, ObjectData, + }, + property::Attribute, + symbol::WellKnownSymbols, + Context, JsNativeError, JsResult, JsValue, +}; + +/// The [`WeakRef`][wr] builtin object. +/// +/// [wr]: https://tc39.es/ecma262/#sec-weak-ref-objects +#[derive(Debug, Clone, Trace, Finalize)] +pub(crate) struct WeakRef; + +impl BuiltIn for WeakRef { + const NAME: &'static str = "WeakRef"; + + const ATTRIBUTE: Attribute = Attribute::WRITABLE.union(Attribute::CONFIGURABLE); + + fn init(context: &mut Context) -> Option { + let _timer = Profiler::global().start_event(Self::NAME, "init"); + ConstructorBuilder::with_standard_constructor( + context, + WeakRef::constructor, + context.intrinsics().constructors().weak_ref().clone(), + ) + .name(Self::NAME) + .length(Self::LENGTH) + .property( + WellKnownSymbols::to_string_tag(), + "WeakRef", + Attribute::CONFIGURABLE, + ) + .method(WeakRef::deref, "deref", 0) + .build() + .conv::() + .pipe(Some) + } +} + +impl WeakRef { + /// The amount of arguments the `WeakRef` constructor takes. + pub(crate) const LENGTH: usize = 1; + + /// Constructor [`WeakRef ( target )`][cons] + /// + /// [cons]: https://tc39.es/ecma262/#sec-weak-ref-target + pub(crate) fn constructor( + new_target: &JsValue, + args: &[JsValue], + context: &mut Context, + ) -> JsResult { + // If NewTarget is undefined, throw a TypeError exception. + if new_target.is_undefined() { + return Err(JsNativeError::typ() + .with_message("WeakRef: cannot call constructor without `new`") + .into()); + } + + // 2. If target is not an Object, throw a TypeError exception. + let target = args.get(0).and_then(JsValue::as_object).ok_or_else(|| { + JsNativeError::typ().with_message(format!( + "WeakRef: expected target argument of type `object`, got target of type `{}`", + args.get_or_undefined(0).type_of() + )) + })?; + + // 3. Let weakRef be ? OrdinaryCreateFromConstructor(NewTarget, "%WeakRef.prototype%", « [[WeakRefTarget]] »). + // 5. Set weakRef.[[WeakRefTarget]] to target. + let weak_ref = JsObject::from_proto_and_data( + get_prototype_from_constructor(new_target, StandardConstructors::weak_ref, context)?, + ObjectData::weak_ref(WeakGc::new(target.inner())), + ); + + // 4. Perform AddToKeptObjects(target). + context.kept_alive.push(target.clone()); + + // 6. Return weakRef. + Ok(weak_ref.into()) + } + + /// Method [`WeakRef.prototype.deref ( )`][spec]. + /// + /// If the referenced object hasn't been collected, this method promotes a `WeakRef` into a + /// proper [`JsObject`], or returns `undefined` otherwise. + /// + /// [spec]: https://tc39.es/ecma262/#sec-weak-ref.prototype.deref + pub(crate) fn deref(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { + // 1. Let weakRef be the this value. + // 2. Perform ? RequireInternalSlot(weakRef, [[WeakRefTarget]]). + let weak_ref = this.as_object().map(JsObject::borrow).ok_or_else(|| { + JsNativeError::typ().with_message(format!( + "WeakRef.prototype.deref: expected `this` to be an `object`, got value of type `{}`", + this.type_of() + )) + })?; + + let weak_ref = weak_ref.as_weak_ref().ok_or_else(|| { + JsNativeError::typ() + .with_message("WeakRef.prototype.deref: expected `this` to be a `WeakRef` object") + })?; + + // 3. Return WeakRefDeref(weakRef). + + // `WeakRefDeref` + // https://tc39.es/ecma262/multipage/managing-memory.html#sec-weakrefderef + // 1. Let target be weakRef.[[WeakRefTarget]]. + // 2. If target is not empty, then + if let Some(object) = weak_ref.upgrade() { + let object = JsObject::from(object); + + // a. Perform AddToKeptObjects(target). + context.kept_alive.push(object.clone()); + + // b. Return target. + Ok(object.into()) + } else { + // 3. Return undefined. + Ok(JsValue::undefined()) + } + } +} + +#[cfg(test)] +mod tests { + use crate::{Context, JsValue}; + + #[test] + fn weak_ref_collected() { + let context = &mut Context::default(); + + assert!(context + .eval( + r#" + var ptr; + { + let obj = {a: 5, b: 6}; + ptr = new WeakRef(obj); + } + ptr.deref() + "# + ) + .unwrap() + .is_object()); + + boa_gc::force_collect(); + + assert_eq!( + context + .eval( + r#" + ptr.deref() + "# + ) + .unwrap(), + JsValue::undefined() + ) + } +} diff --git a/boa_engine/src/bytecompiler/mod.rs b/boa_engine/src/bytecompiler/mod.rs index 33dec83bbc2..8bff029b4e8 100644 --- a/boa_engine/src/bytecompiler/mod.rs +++ b/boa_engine/src/bytecompiler/mod.rs @@ -30,7 +30,7 @@ use boa_ast::{ }, Declaration, Expression, Statement, StatementList, StatementListItem, }; -use boa_gc::Gc; +use boa_gc::{Gc, GcCell}; use boa_interner::{Interner, Sym}; use rustc_hash::FxHashMap; use std::mem::size_of; @@ -265,7 +265,7 @@ impl<'b> ByteCompiler<'b> { #[inline] fn push_compile_environment( &mut self, - environment: Gc>, + environment: Gc>, ) -> usize { let index = self.code_block.compile_environments.len(); self.code_block.compile_environments.push(environment); diff --git a/boa_engine/src/context/intrinsics.rs b/boa_engine/src/context/intrinsics.rs index e8d7dac720e..0da0e55581a 100644 --- a/boa_engine/src/context/intrinsics.rs +++ b/boa_engine/src/context/intrinsics.rs @@ -116,6 +116,7 @@ pub struct StandardConstructors { data_view: StandardConstructor, date_time_format: StandardConstructor, promise: StandardConstructor, + weak_ref: StandardConstructor, } impl Default for StandardConstructors { @@ -175,6 +176,7 @@ impl Default for StandardConstructors { data_view: StandardConstructor::default(), date_time_format: StandardConstructor::default(), promise: StandardConstructor::default(), + weak_ref: StandardConstructor::default(), }; // The value of `Array.prototype` is the Array prototype object. @@ -402,6 +404,11 @@ impl StandardConstructors { pub fn promise(&self) -> &StandardConstructor { &self.promise } + + #[inline] + pub fn weak_ref(&self) -> &StandardConstructor { + &self.weak_ref + } } /// Cached intrinsic objects diff --git a/boa_engine/src/context/mod.rs b/boa_engine/src/context/mod.rs index 47cc742b3d1..0fc3ea736e4 100644 --- a/boa_engine/src/context/mod.rs +++ b/boa_engine/src/context/mod.rs @@ -97,9 +97,15 @@ pub struct Context { #[cfg(feature = "intl")] icu: icu::Icu, + /// Number of instructions remaining before a forced exit + #[cfg(feature = "fuzz")] + pub(crate) instructions_remaining: usize, + pub(crate) vm: Vm, pub(crate) promise_job_queue: VecDeque, + + pub(crate) kept_alive: Vec, } impl Default for Context { @@ -534,6 +540,7 @@ impl Context { self.realm.set_global_binding_number(); let result = self.run(); self.vm.pop_frame(); + self.clear_kept_objects(); self.run_queued_jobs()?; let (result, _) = result?; Ok(result) @@ -543,6 +550,7 @@ impl Context { fn run_queued_jobs(&mut self) -> JsResult<()> { while let Some(job) = self.promise_job_queue.pop_front() { job.call_job_callback(&JsValue::Undefined, &[], self)?; + self.clear_kept_objects(); } Ok(()) } @@ -576,6 +584,18 @@ impl Context { // TODO self.promise_job_queue.push_back(job); } + + /// Abstract operation [`ClearKeptObjects`][clear]. + /// + /// Clears all objects maintained alive by calls to the [`AddToKeptObjects`][add] abstract + /// operation, used within the [`WeakRef`][weak] constructor. + /// + /// [clear]: https://tc39.es/ecma262/multipage/executable-code-and-execution-contexts.html#sec-clear-kept-objects + /// [add]: https://tc39.es/ecma262/multipage/executable-code-and-execution-contexts.html#sec-addtokeptobjects + /// [weak]: https://tc39.es/ecma262/multipage/managing-memory.html#sec-weak-ref-objects + pub fn clear_kept_objects(&mut self) { + self.kept_alive.clear(); + } } /// Builder for the [`Context`] type. /// @@ -593,6 +613,8 @@ pub struct ContextBuilder { interner: Option, #[cfg(feature = "intl")] icu: Option, + #[cfg(feature = "fuzz")] + instructions_remaining: usize, } impl ContextBuilder { @@ -615,6 +637,15 @@ impl ContextBuilder { Ok(self) } + /// Specifies the number of instructions remaining to the [`Context`]. + /// + /// This function is only available if the `fuzz` feature is enabled. + #[cfg(feature = "fuzz")] + pub fn instructions_remaining(mut self, instructions_remaining: usize) -> Self { + self.instructions_remaining = instructions_remaining; + self + } + /// Creates a new [`ContextBuilder`] with a default empty [`Interner`] /// and a default [`BoaProvider`] if the `intl` feature is enabled. pub fn new() -> Self { @@ -643,7 +674,10 @@ impl ContextBuilder { icu::Icu::new(Box::new(icu_testdata::get_provider())) .expect("Failed to initialize default icu data.") }), + #[cfg(feature = "fuzz")] + instructions_remaining: self.instructions_remaining, promise_job_queue: VecDeque::new(), + kept_alive: Vec::new(), }; // Add new builtIns to Context Realm diff --git a/boa_engine/src/environments/compile.rs b/boa_engine/src/environments/compile.rs index 37aa9a64ec4..1ff6f3faf78 100644 --- a/boa_engine/src/environments/compile.rs +++ b/boa_engine/src/environments/compile.rs @@ -2,7 +2,7 @@ use crate::{ environments::runtime::BindingLocator, property::PropertyDescriptor, Context, JsString, JsValue, }; use boa_ast::expression::Identifier; -use boa_gc::{Cell, Finalize, Gc, Trace}; +use boa_gc::{Finalize, Gc, GcCell, Trace}; use rustc_hash::FxHashMap; @@ -22,7 +22,7 @@ struct CompileTimeBinding { /// A compile time environment also indicates, if it is a function environment. #[derive(Debug, Finalize, Trace)] pub(crate) struct CompileTimeEnvironment { - outer: Option>>, + outer: Option>>, environment_index: usize, #[unsafe_ignore_trace] bindings: FxHashMap, @@ -223,7 +223,7 @@ impl Context { let environment_index = self.realm.compile_env.borrow().environment_index + 1; let outer = self.realm.compile_env.clone(); - self.realm.compile_env = Gc::new(Cell::new(CompileTimeEnvironment { + self.realm.compile_env = Gc::new(GcCell::new(CompileTimeEnvironment { outer: Some(outer), environment_index, bindings: FxHashMap::default(), @@ -241,7 +241,7 @@ impl Context { #[inline] pub(crate) fn pop_compile_time_environment( &mut self, - ) -> (usize, Gc>) { + ) -> (usize, Gc>) { let current_env_borrow = self.realm.compile_env.borrow(); if let Some(outer) = ¤t_env_borrow.outer { let outer_clone = outer.clone(); diff --git a/boa_engine/src/environments/runtime.rs b/boa_engine/src/environments/runtime.rs index 3314eb75e51..c06ab965724 100644 --- a/boa_engine/src/environments/runtime.rs +++ b/boa_engine/src/environments/runtime.rs @@ -3,7 +3,7 @@ use std::cell::Cell; use crate::{ environments::CompileTimeEnvironment, error::JsNativeError, object::JsObject, Context, JsValue, }; -use boa_gc::{Cell as GcCell, Finalize, Gc, Trace}; +use boa_gc::{Finalize, Gc, GcCell, Trace}; use boa_ast::expression::Identifier; use rustc_hash::FxHashSet; diff --git a/boa_engine/src/error.rs b/boa_engine/src/error.rs index 00e124da31f..c76bee7f336 100644 --- a/boa_engine/src/error.rs +++ b/boa_engine/src/error.rs @@ -503,6 +503,17 @@ impl JsNativeError { Self::new(JsNativeErrorKind::Uri, Box::default(), None) } + /// Creates a new `JsNativeError` that indicates that the context hit its execution limit. This + /// is only used in a fuzzing context. + #[cfg(feature = "fuzz")] + pub fn no_instructions_remain() -> Self { + Self::new( + JsNativeErrorKind::NoInstructionsRemain, + Box::default(), + None, + ) + } + /// Sets the message of this error. /// /// # Examples @@ -619,6 +630,12 @@ impl JsNativeError { } JsNativeErrorKind::Type => (constructors.type_error().prototype(), ErrorKind::Type), JsNativeErrorKind::Uri => (constructors.uri_error().prototype(), ErrorKind::Uri), + #[cfg(feature = "fuzz")] + JsNativeErrorKind::NoInstructionsRemain => { + unreachable!( + "The NoInstructionsRemain native error cannot be converted to an opaque type." + ) + } }; let o = JsObject::from_proto_and_data(prototype, ObjectData::error(tag)); @@ -747,6 +764,10 @@ pub enum JsNativeErrorKind { /// [e_uri]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURI /// [d_uri]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/decodeURI Uri, + /// Error thrown when no instructions remain. Only used in a fuzzing context; not a valid JS + /// error variant. + #[cfg(feature = "fuzz")] + NoInstructionsRemain, } impl std::fmt::Display for JsNativeErrorKind { @@ -760,6 +781,8 @@ impl std::fmt::Display for JsNativeErrorKind { JsNativeErrorKind::Syntax => "SyntaxError", JsNativeErrorKind::Type => "TypeError", JsNativeErrorKind::Uri => "UriError", + #[cfg(feature = "fuzz")] + JsNativeErrorKind::NoInstructionsRemain => "NoInstructionsRemain", } .fmt(f) } diff --git a/boa_engine/src/job.rs b/boa_engine/src/job.rs index 2a0988b2e57..f24be132a3a 100644 --- a/boa_engine/src/job.rs +++ b/boa_engine/src/job.rs @@ -1,5 +1,5 @@ use crate::{prelude::JsObject, Context, JsResult, JsValue}; -use gc::{Finalize, Trace}; +use boa_gc::{Finalize, Trace}; /// `JobCallback` records /// diff --git a/boa_engine/src/object/jsobject.rs b/boa_engine/src/object/jsobject.rs index d1afe8f99aa..a67a7490ee1 100644 --- a/boa_engine/src/object/jsobject.rs +++ b/boa_engine/src/object/jsobject.rs @@ -10,7 +10,7 @@ use crate::{ value::PreferredType, Context, JsResult, JsValue, }; -use boa_gc::{self, Finalize, Gc, Trace}; +use boa_gc::{self, Finalize, Gc, GcCell, Trace}; use rustc_hash::FxHashMap; use std::{ cell::RefCell, @@ -21,15 +21,15 @@ use std::{ }; /// A wrapper type for an immutably borrowed type T. -pub type Ref<'a, T> = boa_gc::Ref<'a, T>; +pub type Ref<'a, T> = boa_gc::GcCellRef<'a, T>; /// A wrapper type for a mutably borrowed type T. -pub type RefMut<'a, T, U> = boa_gc::RefMut<'a, T, U>; +pub type RefMut<'a, T, U> = boa_gc::GcCellRefMut<'a, T, U>; /// Garbage collected `Object`. #[derive(Trace, Finalize, Clone, Default)] pub struct JsObject { - inner: Gc>, + inner: Gc>, } impl JsObject { @@ -37,7 +37,7 @@ impl JsObject { #[inline] fn from_object(object: Object) -> Self { Self { - inner: Gc::new(boa_gc::Cell::new(object)), + inner: Gc::new(GcCell::new(object)), } } @@ -736,15 +736,26 @@ Cannot both specify accessors and a value or writable attribute", } ) } + + pub(crate) fn inner(&self) -> &Gc> { + &self.inner + } } -impl AsRef> for JsObject { +impl AsRef> for JsObject { #[inline] - fn as_ref(&self) -> &boa_gc::Cell { + fn as_ref(&self) -> &GcCell { &self.inner } } +impl From>> for JsObject { + #[inline] + fn from(inner: Gc>) -> Self { + JsObject { inner } + } +} + impl PartialEq for JsObject { fn eq(&self, other: &Self) -> bool { Self::equals(self, other) diff --git a/boa_engine/src/object/mod.rs b/boa_engine/src/object/mod.rs index 8cbc7fe7a93..64a06e4d0a1 100644 --- a/boa_engine/src/object/mod.rs +++ b/boa_engine/src/object/mod.rs @@ -56,7 +56,7 @@ use crate::{ Context, JsBigInt, JsResult, JsString, JsSymbol, JsValue, }; -use boa_gc::{custom_trace, Finalize, Trace}; +use boa_gc::{custom_trace, Finalize, GcCell, Trace, WeakGc}; use boa_interner::Sym; use rustc_hash::FxHashMap; use std::{ @@ -193,6 +193,7 @@ pub enum ObjectKind { NativeObject(Box), IntegerIndexed(IntegerIndexed), Promise(Promise), + WeakRef(WeakGc>), #[cfg(feature = "intl")] DateTimeFormat(Box), } @@ -222,6 +223,7 @@ unsafe impl Trace for ObjectKind { Self::DateTimeFormat(f) => mark(f), Self::Promise(p) => mark(p), Self::AsyncGenerator(g) => mark(g), + Self::WeakRef(wr) => mark(wr), Self::RegExp(_) | Self::BigInt(_) | Self::Boolean(_) @@ -512,6 +514,14 @@ impl ObjectData { } } + /// Creates the `WeakRef` object data + pub fn weak_ref(weak_ref: WeakGc>) -> Self { + Self { + kind: ObjectKind::WeakRef(weak_ref), + internal_methods: &ORDINARY_INTERNAL_METHODS, + } + } + /// Create the `NativeObject` object data pub fn native_object(native_object: Box) -> Self { Self { @@ -576,6 +586,7 @@ impl Display for ObjectKind { #[cfg(feature = "intl")] Self::DateTimeFormat(_) => "DateTimeFormat", Self::Promise(_) => "Promise", + Self::WeakRef(_) => "WeakRef", }) } } @@ -1424,6 +1435,18 @@ impl Object { } } + /// Gets the `WeakRef`data if the object is a `WeakRef`. + #[inline] + pub fn as_weak_ref(&self) -> Option<&WeakGc>> { + match self.data { + ObjectData { + kind: ObjectKind::WeakRef(ref weak_ref), + .. + } => Some(weak_ref), + _ => None, + } + } + /// Return `true` if it is a native object and the native type is `T`. #[inline] pub fn is(&self) -> bool diff --git a/boa_engine/src/profiler.rs b/boa_engine/src/profiler.rs deleted file mode 100644 index 8b137891791..00000000000 --- a/boa_engine/src/profiler.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/boa_engine/src/realm.rs b/boa_engine/src/realm.rs index 588f01fd16c..ab8cf8dfbd9 100644 --- a/boa_engine/src/realm.rs +++ b/boa_engine/src/realm.rs @@ -8,7 +8,7 @@ use crate::{ environments::{CompileTimeEnvironment, DeclarativeEnvironmentStack}, object::{GlobalPropertyMap, JsObject, JsPrototype, ObjectData, PropertyMap}, }; -use boa_gc::{Cell, Gc}; +use boa_gc::{Gc, GcCell}; use boa_profiler::Profiler; /// Representation of a Realm. @@ -21,7 +21,7 @@ pub struct Realm { pub(crate) global_property_map: PropertyMap, pub(crate) global_prototype: JsPrototype, pub(crate) environments: DeclarativeEnvironmentStack, - pub(crate) compile_env: Gc>, + pub(crate) compile_env: Gc>, } impl Realm { @@ -33,7 +33,7 @@ impl Realm { // Allow identification of the global object easily let global_object = JsObject::from_proto_and_data(None, ObjectData::global()); - let global_compile_environment = Gc::new(Cell::new(CompileTimeEnvironment::new_global())); + let global_compile_environment = Gc::new(GcCell::new(CompileTimeEnvironment::new_global())); Self { global_object, diff --git a/boa_engine/src/string/mod.rs b/boa_engine/src/string/mod.rs index 2b6ea514eb8..2932e76b9b7 100644 --- a/boa_engine/src/string/mod.rs +++ b/boa_engine/src/string/mod.rs @@ -24,7 +24,7 @@ mod common; use crate::{builtins::string::is_trimmable_whitespace, JsBigInt}; -use boa_gc::{unsafe_empty_trace, Finalize, Trace}; +use boa_gc::{empty_trace, Finalize, Trace}; pub use boa_macros::utf16; use std::{ @@ -292,7 +292,7 @@ sa::assert_eq_size!(JsString, *const ()); // Safety: `JsString` does not contain any objects which needs to be traced, so this is safe. unsafe impl Trace for JsString { - unsafe_empty_trace!(); + empty_trace!(); } impl JsString { diff --git a/boa_engine/src/symbol.rs b/boa_engine/src/symbol.rs index f7f44488294..c0b69c841f2 100644 --- a/boa_engine/src/symbol.rs +++ b/boa_engine/src/symbol.rs @@ -16,7 +16,7 @@ //! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol use crate::{js_string, string::utf16, JsString}; -use boa_gc::{unsafe_empty_trace, Finalize, Trace}; +use boa_gc::{empty_trace, Finalize, Trace}; use std::{ cell::Cell, hash::{Hash, Hasher}, @@ -255,7 +255,7 @@ pub struct JsSymbol { // Safety: JsSymbol does not contain any objects which needs to be traced, // so this is safe. unsafe impl Trace for JsSymbol { - unsafe_empty_trace!(); + empty_trace!(); } impl JsSymbol { diff --git a/boa_engine/src/tests.rs b/boa_engine/src/tests.rs index e5febd0d3f6..7e97b8e52a1 100644 --- a/boa_engine/src/tests.rs +++ b/boa_engine/src/tests.rs @@ -2720,7 +2720,7 @@ fn instanceofoperator_rhs_not_object() { assert_eq!( &exec(scenario), - "\"TypeError: right-hand side of 'instanceof' should be an object, got number\"" + "\"TypeError: right-hand side of 'instanceof' should be an object, got `number`\"" ); } diff --git a/boa_engine/src/value/integer.rs b/boa_engine/src/value/integer.rs index d4a1b281331..2cebeeadb31 100644 --- a/boa_engine/src/value/integer.rs +++ b/boa_engine/src/value/integer.rs @@ -30,6 +30,29 @@ impl IntegerOrInfinity { } } +impl From for IntegerOrInfinity { + fn from(number: f64) -> Self { + // `ToIntegerOrInfinity ( argument )` + if number.is_nan() || number == 0.0 { + // 2. If number is NaN, +0𝔽, or -0𝔽, return 0. + Self::Integer(0) + } else if number == f64::INFINITY { + // 3. If number is +∞𝔽, return +∞. + Self::PositiveInfinity + } else if number == f64::NEG_INFINITY { + // 4. If number is -∞𝔽, return -∞. + Self::NegativeInfinity + } else { + // 5. Let integer be floor(abs(ℝ(number))). + // 6. If number < +0𝔽, set integer to -integer. + let integer = number.abs().floor().copysign(number) as i64; + + // 7. Return integer. + Self::Integer(integer) + } + } +} + impl PartialEq for IntegerOrInfinity { fn eq(&self, other: &i64) -> bool { match self { diff --git a/boa_engine/src/value/mod.rs b/boa_engine/src/value/mod.rs index a5df0b672cf..473a1029d5f 100644 --- a/boa_engine/src/value/mod.rs +++ b/boa_engine/src/value/mod.rs @@ -865,23 +865,8 @@ impl JsValue { // 1. Let number be ? ToNumber(argument). let number = self.to_number(context)?; - if number.is_nan() || number == 0.0 { - // 2. If number is NaN, +0𝔽, or -0𝔽, return 0. - Ok(IntegerOrInfinity::Integer(0)) - } else if number == f64::INFINITY { - // 3. If number is +∞𝔽, return +∞. - Ok(IntegerOrInfinity::PositiveInfinity) - } else if number == f64::NEG_INFINITY { - // 4. If number is -∞𝔽, return -∞. - Ok(IntegerOrInfinity::NegativeInfinity) - } else { - // 5. Let integer be floor(abs(ℝ(number))). - // 6. If number < +0𝔽, set integer to -integer. - let integer = number.abs().floor().copysign(number) as i64; - - // 7. Return integer. - Ok(IntegerOrInfinity::Integer(integer)) - } + // Continues on `IntegerOrInfinity::from::` + Ok(IntegerOrInfinity::from(number)) } /// Converts a value to a double precision floating point. @@ -964,7 +949,7 @@ impl JsValue { /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-typeof-operator - pub fn type_of(&self) -> JsString { + pub fn type_of(&self) -> &'static str { match *self { Self::Rational(_) | Self::Integer(_) => "number", Self::String(_) => "string", @@ -981,7 +966,6 @@ impl JsValue { } } } - .into() } /// Abstract operation `IsArray ( argument )` diff --git a/boa_engine/src/value/operations.rs b/boa_engine/src/value/operations.rs index f22478f8627..45dcaaf22d9 100644 --- a/boa_engine/src/value/operations.rs +++ b/boa_engine/src/value/operations.rs @@ -421,8 +421,8 @@ impl JsValue { if !target.is_object() { return Err(JsNativeError::typ() .with_message(format!( - "right-hand side of 'instanceof' should be an object, got {}", - target.type_of().to_std_string_escaped() + "right-hand side of 'instanceof' should be an object, got `{}`", + target.type_of() )) .into()); } diff --git a/boa_engine/src/vm/code_block.rs b/boa_engine/src/vm/code_block.rs index a8444e20725..b79ddf3b4bf 100644 --- a/boa_engine/src/vm/code_block.rs +++ b/boa_engine/src/vm/code_block.rs @@ -24,7 +24,7 @@ use crate::{ Context, JsResult, JsString, JsValue, }; use boa_ast::{expression::Identifier, function::FormalParameterList}; -use boa_gc::{Cell, Finalize, Gc, Trace}; +use boa_gc::{Finalize, Gc, GcCell, Trace}; use boa_interner::{Interner, Sym, ToInternedString}; use boa_profiler::Profiler; use std::{collections::VecDeque, convert::TryInto, mem::size_of}; @@ -103,7 +103,7 @@ pub struct CodeBlock { pub(crate) arguments_binding: Option, /// Compile time environments in this function. - pub(crate) compile_environments: Vec>>, + pub(crate) compile_environments: Vec>>, /// The `[[IsClassConstructor]]` internal slot. pub(crate) is_class_constructor: bool, @@ -434,7 +434,7 @@ impl ToInternedString for CodeBlock { for (i, value) in self.literals.iter().enumerate() { f.push_str(&format!( " {i:04}: <{}> {}\n", - value.type_of().to_std_string_escaped(), + value.type_of(), value.display() )); } @@ -1099,7 +1099,7 @@ impl JsObject { prototype, ObjectData::generator(Generator { state: GeneratorState::SuspendedStart, - context: Some(Gc::new(Cell::new(GeneratorContext { + context: Some(Gc::new(GcCell::new(GeneratorContext { environments, call_frame, stack, @@ -1242,7 +1242,7 @@ impl JsObject { prototype, ObjectData::async_generator(AsyncGenerator { state: AsyncGeneratorState::SuspendedStart, - context: Some(Gc::new(Cell::new(GeneratorContext { + context: Some(Gc::new(GcCell::new(GeneratorContext { environments, call_frame, stack, diff --git a/boa_engine/src/vm/mod.rs b/boa_engine/src/vm/mod.rs index 906c116c8d4..9fd6d5b4c41 100644 --- a/boa_engine/src/vm/mod.rs +++ b/boa_engine/src/vm/mod.rs @@ -7,6 +7,8 @@ use crate::{ vm::{call_frame::CatchAddresses, code_block::Readable}, Context, JsResult, JsValue, }; +#[cfg(feature = "fuzz")] +use crate::{JsError, JsNativeError}; use boa_interner::ToInternedString; use boa_profiler::Profiler; use std::{convert::TryInto, mem::size_of, time::Instant}; @@ -182,6 +184,13 @@ impl Context { }); while self.vm.frame().pc < self.vm.frame().code.code.len() { + #[cfg(feature = "fuzz")] + if self.instructions_remaining == 0 { + return Err(JsError::from_native(JsNativeError::no_instructions_remain())); + } else { + self.instructions_remaining -= 1; + } + let result = if self.vm.trace { let mut pc = self.vm.frame().pc; let opcode: Opcode = self diff --git a/boa_engine/src/vm/opcode/binary_ops/mod.rs b/boa_engine/src/vm/opcode/binary_ops/mod.rs index ef2854ef65a..31d1a04b682 100644 --- a/boa_engine/src/vm/opcode/binary_ops/mod.rs +++ b/boa_engine/src/vm/opcode/binary_ops/mod.rs @@ -86,8 +86,8 @@ impl Operation for In { if !rhs.is_object() { return Err(JsNativeError::typ() .with_message(format!( - "right-hand side of 'in' should be an object, got {}", - rhs.type_of().to_std_string_escaped() + "right-hand side of 'in' should be an object, got `{}`", + rhs.type_of() )) .into()); } diff --git a/boa_examples/Cargo.toml b/boa_examples/Cargo.toml index 28b1be49752..b6135a7dc82 100644 --- a/boa_examples/Cargo.toml +++ b/boa_examples/Cargo.toml @@ -17,4 +17,3 @@ boa_ast.workspace = true boa_interner.workspace = true boa_gc.workspace = true boa_parser.workspace = true -gc = "0.4.1" diff --git a/boa_gc/Cargo.toml b/boa_gc/Cargo.toml index 5cca97b53be..39f1afa64c1 100644 --- a/boa_gc/Cargo.toml +++ b/boa_gc/Cargo.toml @@ -11,7 +11,5 @@ repository.workspace = true rust-version.workspace = true [dependencies] -gc = { version = "0.4.1", features = ["derive"] } - -# Optional Dependencies -measureme = { version = "10.1.0", optional = true } +boa_profiler.workspace = true +boa_macros.workspace = true diff --git a/boa_gc/src/cell.rs b/boa_gc/src/cell.rs new file mode 100644 index 00000000000..953f0e4edf9 --- /dev/null +++ b/boa_gc/src/cell.rs @@ -0,0 +1,597 @@ +//! A garbage collected cell implementation + +use crate::trace::{Finalize, Trace}; +use std::{ + cell::{Cell, UnsafeCell}, + cmp::Ordering, + fmt::{self, Debug, Display}, + hash::Hash, + ops::{Deref, DerefMut}, +}; + +/// `BorrowFlag` represent the internal state of a `GcCell` and +/// keeps track of the amount of current borrows. +#[derive(Copy, Clone)] +pub(crate) struct BorrowFlag(usize); + +/// `BorrowState` represents the various states of a `BorrowFlag` +/// +/// - Reading: the value is currently being read/borrowed. +/// - Writing: the value is currently being written/borrowed mutably. +/// - Unused: the value is currently unrooted. +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub(crate) enum BorrowState { + Reading, + Writing, + Unused, +} + +const ROOT: usize = 1; +const WRITING: usize = !1; +const UNUSED: usize = 0; + +/// The base borrowflag init is rooted, and has no outstanding borrows. +pub(crate) const BORROWFLAG_INIT: BorrowFlag = BorrowFlag(ROOT); + +impl BorrowFlag { + /// Check the current `BorrowState` of `BorrowFlag`. + #[inline] + pub(crate) const fn borrowed(self) -> BorrowState { + match self.0 & !ROOT { + UNUSED => BorrowState::Unused, + WRITING => BorrowState::Writing, + _ => BorrowState::Reading, + } + } + + /// Check whether the borrow bit is flagged. + #[inline] + pub(crate) const fn rooted(self) -> bool { + self.0 & ROOT > 0 + } + + /// Set the `BorrowFlag`'s state to writing. + #[inline] + pub(crate) const fn set_writing(self) -> Self { + // Set every bit other than the root bit, which is preserved + Self(self.0 | WRITING) + } + + /// Remove the root flag on `BorrowFlag` + #[inline] + pub(crate) const fn set_unused(self) -> Self { + // Clear every bit other than the root bit, which is preserved + Self(self.0 & ROOT) + } + + /// Increments the counter for a new borrow. + /// + /// # Panic + /// - This method will panic if the current `BorrowState` is writing. + /// - This method will panic after incrementing if the borrow count overflows. + #[inline] + pub(crate) fn add_reading(self) -> Self { + assert!(self.borrowed() != BorrowState::Writing); + // Add 1 to the integer starting at the second binary digit. As our + // borrowstate is not writing, we know that overflow cannot happen, so + // this is equivalent to the following, more complicated, expression: + // + // BorrowFlag((self.0 & ROOT) | (((self.0 >> 1) + 1) << 1)) + let flags = Self(self.0 + 0b10); + + // This will fail if the borrow count overflows, which shouldn't happen, + // but let's be safe + { + assert!(flags.borrowed() == BorrowState::Reading); + } + flags + } + + /// Decrements the counter to remove a borrow. + /// + /// # Panic + /// - This method will panic if the current `BorrowState` is not reading. + #[inline] + pub(crate) fn sub_reading(self) -> Self { + assert!(self.borrowed() == BorrowState::Reading); + // Subtract 1 from the integer starting at the second binary digit. As + // our borrowstate is not writing or unused, we know that overflow or + // undeflow cannot happen, so this is equivalent to the following, more + // complicated, expression: + // + // BorrowFlag((self.0 & ROOT) | (((self.0 >> 1) - 1) << 1)) + Self(self.0 - 0b10) + } + + /// Set the root flag on the `BorrowFlag`. + #[inline] + pub(crate) fn set_rooted(self, rooted: bool) -> Self { + // Preserve the non-root bits + Self((self.0 & !ROOT) | (usize::from(rooted))) + } +} + +impl Debug for BorrowFlag { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("BorrowFlag") + .field("Rooted", &self.rooted()) + .field("State", &self.borrowed()) + .finish() + } +} + +/// A mutable memory location with dynamically checked borrow rules +/// that can be used inside of a garbage-collected pointer. +/// +/// This object is a `RefCell` that can be used inside of a `Gc`. +pub struct GcCell { + pub(crate) flags: Cell, + pub(crate) cell: UnsafeCell, +} + +impl GcCell { + /// Creates a new `GcCell` containing `value`. + #[inline] + pub const fn new(value: T) -> Self { + Self { + flags: Cell::new(BORROWFLAG_INIT), + cell: UnsafeCell::new(value), + } + } + + /// Consumes the `GcCell`, returning the wrapped value. + #[inline] + pub fn into_inner(self) -> T { + self.cell.into_inner() + } +} + +impl GcCell { + /// Immutably borrows the wrapped value. + /// + /// The borrow lasts until the returned `GcCellRef` exits scope. + /// Multiple immutable borrows can be taken out at the same time. + /// + /// # Panics + /// + /// Panics if the value is currently mutably borrowed. + #[inline] + pub fn borrow(&self) -> GcCellRef<'_, T> { + match self.try_borrow() { + Ok(value) => value, + Err(e) => panic!("{}", e), + } + } + + /// Mutably borrows the wrapped value. + /// + /// The borrow lasts until the returned `GcCellRefMut` exits scope. + /// The value cannot be borrowed while this borrow is active. + /// + /// # Panics + /// + /// Panics if the value is currently borrowed. + #[inline] + pub fn borrow_mut(&self) -> GcCellRefMut<'_, T> { + match self.try_borrow_mut() { + Ok(value) => value, + Err(e) => panic!("{}", e), + } + } + + /// Immutably borrows the wrapped value, returning an error if the value is currently mutably + /// borrowed. + /// + /// The borrow lasts until the returned `GcCellRef` exits scope. Multiple immutable borrows can be + /// taken out at the same time. + /// + /// This is the non-panicking variant of [`borrow`](#method.borrow). + /// + /// # Errors + /// + /// Returns an `Err` if the value is currently mutably borrowed. + pub fn try_borrow(&self) -> Result, BorrowError> { + if self.flags.get().borrowed() == BorrowState::Writing { + return Err(BorrowError); + } + self.flags.set(self.flags.get().add_reading()); + + // SAFETY: calling value on a rooted value may cause Undefined Behavior + unsafe { + Ok(GcCellRef { + flags: &self.flags, + value: &*self.cell.get(), + }) + } + } + + /// Mutably borrows the wrapped value, returning an error if the value is currently borrowed. + /// + /// The borrow lasts until the returned `GcCellRefMut` exits scope. + /// The value cannot be borrowed while this borrow is active. + /// + /// This is the non-panicking variant of [`borrow_mut`](#method.borrow_mut). + /// + /// # Errors + /// + /// Returns an `Err` if the value is currently borrowed. + pub fn try_borrow_mut(&self) -> Result, BorrowMutError> { + if self.flags.get().borrowed() != BorrowState::Unused { + return Err(BorrowMutError); + } + self.flags.set(self.flags.get().set_writing()); + + // SAFETY: This is safe as the value is rooted if it was not previously rooted, + // so it cannot be dropped. + unsafe { + // Force the val_ref's contents to be rooted for the duration of the + // mutable borrow + if !self.flags.get().rooted() { + (*self.cell.get()).root(); + } + + Ok(GcCellRefMut { + gc_cell: self, + value: &mut *self.cell.get(), + }) + } + } +} + +/// An error returned by [`GcCell::try_borrow`](struct.GcCell.html#method.try_borrow). +#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Default, Hash)] +pub struct BorrowError; + +impl Display for BorrowError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + Display::fmt("GcCell already mutably borrowed", f) + } +} + +/// An error returned by [`GcCell::try_borrow_mut`](struct.GcCell.html#method.try_borrow_mut). +#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Default, Hash)] +pub struct BorrowMutError; + +impl Display for BorrowMutError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + Display::fmt("GcCell already borrowed", f) + } +} + +impl Finalize for GcCell {} + +// SAFETY: GcCell maintains it's own BorrowState and rootedness. GcCell's implementation +// focuses on only continuing Trace based methods while the cell state is not written. +// Implementing a Trace while the cell is being written to or incorrectly implementing Trace +// on GcCell's value may cause Undefined Behavior +unsafe impl Trace for GcCell { + #[inline] + unsafe fn trace(&self) { + match self.flags.get().borrowed() { + BorrowState::Writing => (), + // SAFETY: Please see GcCell's Trace impl Safety note. + _ => unsafe { (*self.cell.get()).trace() }, + } + } + + #[inline] + unsafe fn weak_trace(&self) { + match self.flags.get().borrowed() { + BorrowState::Writing => (), + // SAFETY: Please see GcCell's Trace impl Safety note. + _ => unsafe { (*self.cell.get()).weak_trace() }, + } + } + + unsafe fn root(&self) { + assert!(!self.flags.get().rooted(), "Can't root a GcCell twice!"); + self.flags.set(self.flags.get().set_rooted(true)); + + match self.flags.get().borrowed() { + BorrowState::Writing => (), + // SAFETY: Please see GcCell's Trace impl Safety note. + _ => unsafe { (*self.cell.get()).root() }, + } + } + + #[inline] + unsafe fn unroot(&self) { + assert!(self.flags.get().rooted(), "Can't unroot a GcCell twice!"); + self.flags.set(self.flags.get().set_rooted(false)); + + match self.flags.get().borrowed() { + BorrowState::Writing => (), + // SAFETY: Please see GcCell's Trace impl Safety note. + _ => unsafe { (*self.cell.get()).unroot() }, + } + } + + #[inline] + fn run_finalizer(&self) { + Finalize::finalize(self); + match self.flags.get().borrowed() { + BorrowState::Writing => (), + // SAFETY: Please see GcCell's Trace impl Safety note. + _ => unsafe { (*self.cell.get()).run_finalizer() }, + } + } +} + +/// A wrapper type for an immutably borrowed value from a `GcCell`. +pub struct GcCellRef<'a, T: ?Sized + 'static> { + pub(crate) flags: &'a Cell, + pub(crate) value: &'a T, +} + +impl<'a, T: ?Sized> GcCellRef<'a, T> { + /// Copies a `GcCellRef`. + /// + /// The `GcCell` is already immutably borrowed, so this cannot fail. + /// + /// This is an associated function that needs to be used as + /// `GcCellRef::clone(...)`. A `Clone` implementation or a method + /// would interfere with the use of `c.borrow().clone()` to clone + /// the contents of a `GcCell`. + #[inline] + #[allow(clippy::should_implement_trait)] + #[must_use] + pub fn clone(orig: &GcCellRef<'a, T>) -> GcCellRef<'a, T> { + orig.flags.set(orig.flags.get().add_reading()); + GcCellRef { + flags: orig.flags, + value: orig.value, + } + } + + /// Makes a new `GcCellRef` from a component of the borrowed data. + /// + /// The `GcCell` is already immutably borrowed, so this cannot fail. + /// + /// This is an associated function that needs to be used as `GcCellRef::map(...)`. + /// A method would interfere with methods of the same name on the contents + /// of a `GcCellRef` used through `Deref`. + #[inline] + pub fn map(orig: Self, f: F) -> GcCellRef<'a, U> + where + U: ?Sized, + F: FnOnce(&T) -> &U, + { + let ret = GcCellRef { + flags: orig.flags, + value: f(orig.value), + }; + + // We have to tell the compiler not to call the destructor of GcCellRef, + // because it will update the borrow flags. + std::mem::forget(orig); + + ret + } + + /// Splits a `GcCellRef` into multiple `GcCellRef`s for different components of the borrowed data. + /// + /// The `GcCell` is already immutably borrowed, so this cannot fail. + /// + /// This is an associated function that needs to be used as `GcCellRef::map_split(...)`. + /// A method would interfere with methods of the same name on the contents of a `GcCellRef` used through `Deref`. + #[inline] + pub fn map_split(orig: Self, f: F) -> (GcCellRef<'a, U>, GcCellRef<'a, V>) + where + U: ?Sized, + V: ?Sized, + F: FnOnce(&T) -> (&U, &V), + { + let (a, b) = f(orig.value); + + orig.flags.set(orig.flags.get().add_reading()); + + let ret = ( + GcCellRef { + flags: orig.flags, + value: a, + }, + GcCellRef { + flags: orig.flags, + value: b, + }, + ); + + // We have to tell the compiler not to call the destructor of GcCellRef, + // because it will update the borrow flags. + std::mem::forget(orig); + + ret + } +} + +impl Deref for GcCellRef<'_, T> { + type Target = T; + + #[inline] + fn deref(&self) -> &T { + self.value + } +} + +impl Drop for GcCellRef<'_, T> { + fn drop(&mut self) { + debug_assert!(self.flags.get().borrowed() == BorrowState::Reading); + self.flags.set(self.flags.get().sub_reading()); + } +} + +impl Debug for GcCellRef<'_, T> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + Debug::fmt(&**self, f) + } +} + +impl Display for GcCellRef<'_, T> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + Display::fmt(&**self, f) + } +} + +/// A wrapper type for a mutably borrowed value from a `GcCell`. +pub struct GcCellRefMut<'a, T: Trace + ?Sized + 'static, U: ?Sized = T> { + pub(crate) gc_cell: &'a GcCell, + pub(crate) value: &'a mut U, +} + +impl<'a, T: Trace + ?Sized, U: ?Sized> GcCellRefMut<'a, T, U> { + /// Makes a new `GcCellRefMut` for a component of the borrowed data, e.g., an enum + /// variant. + /// + /// The `GcCellRefMut` is already mutably borrowed, so this cannot fail. + /// + /// This is an associated function that needs to be used as + /// `GcCellRefMut::map(...)`. A method would interfere with methods of the same + /// name on the contents of a `GcCell` used through `Deref`. + #[inline] + pub fn map(orig: Self, f: F) -> GcCellRefMut<'a, T, V> + where + V: ?Sized, + F: FnOnce(&mut U) -> &mut V, + { + // SAFETY: This is safe as `GcCellRefMut` is already borrowed, so the value is rooted. + #[allow(trivial_casts)] + let value = unsafe { &mut *(orig.value as *mut U) }; + + let ret = GcCellRefMut { + gc_cell: orig.gc_cell, + value: f(value), + }; + + // We have to tell the compiler not to call the destructor of GcCellRefMut, + // because it will update the borrow flags. + std::mem::forget(orig); + + ret + } +} + +impl Deref for GcCellRefMut<'_, T, U> { + type Target = U; + + #[inline] + fn deref(&self) -> &U { + self.value + } +} + +impl DerefMut for GcCellRefMut<'_, T, U> { + #[inline] + fn deref_mut(&mut self) -> &mut U { + self.value + } +} + +impl Drop for GcCellRefMut<'_, T, U> { + #[inline] + fn drop(&mut self) { + debug_assert!(self.gc_cell.flags.get().borrowed() == BorrowState::Writing); + // Restore the rooted state of the GcCell's contents to the state of the GcCell. + // During the lifetime of the GcCellRefMut, the GcCell's contents are rooted. + if !self.gc_cell.flags.get().rooted() { + // SAFETY: If `GcCell` is no longer rooted, then unroot it. This should be safe + // as the internal `GcBox` should be guaranteed to have at least 1 root. + unsafe { + (*self.gc_cell.cell.get()).unroot(); + } + } + self.gc_cell + .flags + .set(self.gc_cell.flags.get().set_unused()); + } +} + +impl Debug for GcCellRefMut<'_, T, U> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + Debug::fmt(&**self, f) + } +} + +impl Display for GcCellRefMut<'_, T, U> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + Display::fmt(&**self, f) + } +} + +// SAFETY: GcCell tracks it's `BorrowState` is `Writing` +unsafe impl Send for GcCell {} + +impl Clone for GcCell { + #[inline] + fn clone(&self) -> Self { + Self::new(self.borrow().clone()) + } +} + +impl Default for GcCell { + #[inline] + fn default() -> Self { + Self::new(Default::default()) + } +} + +#[allow(clippy::inline_always)] +impl PartialEq for GcCell { + #[inline(always)] + fn eq(&self, other: &Self) -> bool { + *self.borrow() == *other.borrow() + } +} + +impl Eq for GcCell {} + +#[allow(clippy::inline_always)] +impl PartialOrd for GcCell { + #[inline(always)] + fn partial_cmp(&self, other: &Self) -> Option { + (*self.borrow()).partial_cmp(&*other.borrow()) + } + + #[inline(always)] + fn lt(&self, other: &Self) -> bool { + *self.borrow() < *other.borrow() + } + + #[inline(always)] + fn le(&self, other: &Self) -> bool { + *self.borrow() <= *other.borrow() + } + + #[inline(always)] + fn gt(&self, other: &Self) -> bool { + *self.borrow() > *other.borrow() + } + + #[inline(always)] + fn ge(&self, other: &Self) -> bool { + *self.borrow() >= *other.borrow() + } +} + +impl Ord for GcCell { + #[inline] + fn cmp(&self, other: &Self) -> Ordering { + (*self.borrow()).cmp(&*other.borrow()) + } +} + +impl Debug for GcCell { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self.flags.get().borrowed() { + BorrowState::Unused | BorrowState::Reading => f + .debug_struct("GcCell") + .field("flags", &self.flags.get()) + .field("value", &self.borrow()) + .finish(), + BorrowState::Writing => f + .debug_struct("GcCell") + .field("flags", &self.flags.get()) + .field("value", &"") + .finish(), + } + } +} diff --git a/boa_gc/src/internals/ephemeron_box.rs b/boa_gc/src/internals/ephemeron_box.rs new file mode 100644 index 00000000000..0056736eef6 --- /dev/null +++ b/boa_gc/src/internals/ephemeron_box.rs @@ -0,0 +1,129 @@ +use crate::{finalizer_safe, trace::Trace, Finalize, Gc, GcBox}; +use std::{cell::Cell, ptr::NonNull}; + +/// The inner allocation of an [`Ephemeron`][crate::Ephemeron] pointer. +pub(crate) struct EphemeronBox { + key: Cell>>>, + value: V, +} + +impl EphemeronBox { + pub(crate) fn new(key: &Gc, value: V) -> Self { + Self { + key: Cell::new(Some(key.inner_ptr())), + value, + } + } +} + +impl EphemeronBox { + /// Checks if the key pointer is marked by Trace + #[inline] + pub(crate) fn is_marked(&self) -> bool { + self.inner_key().map_or(false, GcBox::is_marked) + } + + /// Returns some pointer to the `key`'s `GcBox` or None + /// # Panics + /// This method will panic if called while the garbage collector is dropping. + #[inline] + pub(crate) fn inner_key_ptr(&self) -> Option<*mut GcBox> { + assert!(finalizer_safe()); + self.key.get().map(NonNull::as_ptr) + } + + /// Returns some reference to `key`'s `GcBox` or None + #[inline] + pub(crate) fn inner_key(&self) -> Option<&GcBox> { + // SAFETY: This is safe as `EphemeronBox::inner_key_ptr()` will + // fetch either a live `GcBox` or None. The value of `key` is set + // to None in the case where `EphemeronBox` and `key`'s `GcBox` + // entered into `Collector::sweep()` as unmarked. + unsafe { self.inner_key_ptr().map(|inner_key| &*inner_key) } + } + + /// Returns a reference to the value of `key`'s `GcBox` + #[inline] + pub(crate) fn key(&self) -> Option<&K> { + self.inner_key().map(GcBox::value) + } + + /// Returns a reference to `value` + #[inline] + pub(crate) const fn value(&self) -> &V { + &self.value + } + + /// Calls [`Trace::weak_trace()`][crate::Trace] on key + #[inline] + fn weak_trace_key(&self) { + if let Some(key) = self.inner_key() { + key.weak_trace_inner(); + } + } + + /// Calls [`Trace::weak_trace()`][crate::Trace] on value + #[inline] + fn weak_trace_value(&self) { + // SAFETY: Value is a sized element that must implement trace. The + // operation is safe as EphemeronBox owns value and `Trace::weak_trace` + // must be implemented on it + unsafe { + self.value().weak_trace(); + } + } +} + +// `EphemeronBox`'s Finalize is special in that if it is determined to be unreachable +// and therefore so has the `GcBox` that `key`stores the pointer to, then we set `key` +// to None to guarantee that we do not access freed memory. +impl Finalize for EphemeronBox { + #[inline] + fn finalize(&self) { + self.key.set(None); + } +} + +// SAFETY: EphemeronBox implements primarly two methods of trace `Trace::is_marked_ephemeron` +// to determine whether the key field is stored and `Trace::weak_trace` which continues the `Trace::weak_trace()` +// into `key` and `value`. +unsafe impl Trace for EphemeronBox { + #[inline] + unsafe fn trace(&self) { + /* An ephemeron is never traced with Phase One Trace */ + } + + /// Checks if the `key`'s `GcBox` has been marked by `Trace::trace()` or `Trace::weak_trace`. + #[inline] + fn is_marked_ephemeron(&self) -> bool { + self.is_marked() + } + + /// Checks if this `EphemeronBox` has already been determined reachable. If so, continue to trace + /// value in `key` and `value`. + #[inline] + unsafe fn weak_trace(&self) { + if self.is_marked() { + self.weak_trace_key(); + self.weak_trace_value(); + } + } + + // EphemeronBox does not implement root. + #[inline] + unsafe fn root(&self) {} + + // EphemeronBox does not implement unroot + #[inline] + unsafe fn unroot(&self) {} + + // An `EphemeronBox`'s key is set to None once it has been finalized. + // + // NOTE: while it is possible for the `key`'s pointer value to be + // resurrected, we should still consider the finalize the ephemeron + // box and set the `key` to None. + #[inline] + fn run_finalizer(&self) { + Finalize::finalize(self); + } +} diff --git a/boa_gc/src/internals/gc_box.rs b/boa_gc/src/internals/gc_box.rs new file mode 100644 index 00000000000..9c5ac998d9b --- /dev/null +++ b/boa_gc/src/internals/gc_box.rs @@ -0,0 +1,197 @@ +use crate::Trace; +use std::{ + cell::Cell, + fmt, + ptr::{self, NonNull}, +}; + +// Age and Weak Flags +const MARK_MASK: usize = 1 << (usize::BITS - 2); +const WEAK_MASK: usize = 1 << (usize::BITS - 1); +const ROOTS_MASK: usize = !(MARK_MASK | WEAK_MASK); +const ROOTS_MAX: usize = ROOTS_MASK; + +/// The `GcBoxheader` contains the `GcBox`'s current state for the `Collector`'s +/// Mark/Sweep as well as a pointer to the next node in the heap. +/// +/// These flags include: +/// - Root Count +/// - Mark Flag Bit +/// - Weak Flag Bit +/// +/// The next node is set by the `Allocator` during initialization and by the +/// `Collector` during the sweep phase. +pub(crate) struct GcBoxHeader { + roots: Cell, + pub(crate) next: Cell>>>, +} + +impl GcBoxHeader { + /// Creates a new `GcBoxHeader` with a root of 1 and next set to None. + #[inline] + pub(crate) fn new() -> Self { + Self { + roots: Cell::new(1), + next: Cell::new(None), + } + } + + /// Creates a new `GcBoxHeader` with the Weak bit at 1 and roots of 1. + #[inline] + pub(crate) fn new_weak() -> Self { + // Set weak_flag + Self { + roots: Cell::new(WEAK_MASK | 1), + next: Cell::new(None), + } + } + + /// Returns the `GcBoxHeader`'s current root count + #[inline] + pub(crate) fn roots(&self) -> usize { + self.roots.get() & ROOTS_MASK + } + + /// Increments `GcBoxHeader`'s root count. + #[inline] + pub(crate) fn inc_roots(&self) { + let roots = self.roots.get(); + + if (roots & ROOTS_MASK) < ROOTS_MAX { + self.roots.set(roots + 1); + } else { + // TODO: implement a better way to handle root overload. + panic!("roots counter overflow"); + } + } + + /// Decreases `GcBoxHeader`'s current root count. + #[inline] + pub(crate) fn dec_roots(&self) { + // Underflow check as a stop gap for current issue when dropping. + if self.roots.get() > 0 { + self.roots.set(self.roots.get() - 1); + } + } + + /// Returns a bool for whether `GcBoxHeader`'s mark bit is 1. + #[inline] + pub(crate) fn is_marked(&self) -> bool { + self.roots.get() & MARK_MASK != 0 + } + + /// Sets `GcBoxHeader`'s mark bit to 1. + #[inline] + pub(crate) fn mark(&self) { + self.roots.set(self.roots.get() | MARK_MASK); + } + + /// Sets `GcBoxHeader`'s mark bit to 0. + #[inline] + pub(crate) fn unmark(&self) { + self.roots.set(self.roots.get() & !MARK_MASK); + } + + /// Returns a bool for whether the `GcBoxHeader`'s weak bit is 1. + #[inline] + pub(crate) fn is_ephemeron(&self) -> bool { + self.roots.get() & WEAK_MASK != 0 + } +} + +impl fmt::Debug for GcBoxHeader { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("GcBoxHeader") + .field("Roots", &self.roots()) + .field("Weak", &self.is_ephemeron()) + .field("Marked", &self.is_marked()) + .finish() + } +} + +/// A garbage collected allocation. +#[derive(Debug)] +pub(crate) struct GcBox { + pub(crate) header: GcBoxHeader, + pub(crate) value: T, +} + +impl GcBox { + /// Returns a new `GcBox` with a rooted `GcBoxHeader`. + pub(crate) fn new(value: T) -> Self { + Self { + header: GcBoxHeader::new(), + value, + } + } + + /// Returns a new `GcBox` with a rooted and weak `GcBoxHeader`. + pub(crate) fn new_weak(value: T) -> Self { + Self { + header: GcBoxHeader::new_weak(), + value, + } + } +} + +impl GcBox { + /// Returns `true` if the two references refer to the same `GcBox`. + pub(crate) fn ptr_eq(this: &Self, other: &Self) -> bool { + // Use .header to ignore fat pointer vtables, to work around + // https://github.com/rust-lang/rust/issues/46139 + ptr::eq(&this.header, &other.header) + } + + /// Marks this `GcBox` and marks through its data. + #[inline] + pub(crate) unsafe fn trace_inner(&self) { + if !self.header.is_marked() && !self.header.is_ephemeron() { + self.header.mark(); + // SAFETY: if `GcBox::trace_inner()` has been called, then, + // this box must have been deemed as reachable via tracing + // from a root, which by extension means that value has not + // been dropped either. + unsafe { + self.value.trace(); + } + } + } + + /// Trace inner data + #[inline] + pub(crate) fn weak_trace_inner(&self) { + // SAFETY: if a `GcBox` has `weak_trace_inner` called, then the inner. + // value must have been deemed as reachable. + unsafe { + self.value.weak_trace(); + } + } + + /// Increases the root count on this `GcBox`. + /// + /// Roots prevent the `GcBox` from being destroyed by the garbage collector. + #[inline] + pub(crate) fn root_inner(&self) { + self.header.inc_roots(); + } + + /// Decreases the root count on this `GcBox`. + /// + /// Roots prevent the `GcBox` from being destroyed by the garbage collector. + #[inline] + pub(crate) fn unroot_inner(&self) { + self.header.dec_roots(); + } + + /// Returns a reference to the `GcBox`'s value. + #[inline] + pub(crate) const fn value(&self) -> &T { + &self.value + } + + /// Returns a bool for whether the header is marked. + #[inline] + pub(crate) fn is_marked(&self) -> bool { + self.header.is_marked() + } +} diff --git a/boa_gc/src/internals/mod.rs b/boa_gc/src/internals/mod.rs new file mode 100644 index 00000000000..7539365723b --- /dev/null +++ b/boa_gc/src/internals/mod.rs @@ -0,0 +1,4 @@ +mod ephemeron_box; +mod gc_box; + +pub(crate) use self::{ephemeron_box::EphemeronBox, gc_box::GcBox}; diff --git a/boa_gc/src/lib.rs b/boa_gc/src/lib.rs index ac24531793c..6db1b8d614f 100644 --- a/boa_gc/src/lib.rs +++ b/boa_gc/src/lib.rs @@ -1,6 +1,398 @@ //! Garbage collector for the Boa JavaScript engine. -pub use gc::{ - custom_trace, finalizer_safe, force_collect, unsafe_empty_trace, Finalize, Gc, GcCell as Cell, - GcCellRef as Ref, GcCellRefMut as RefMut, Trace, +#![cfg_attr(not(test), forbid(clippy::unwrap_used))] +#![warn(missing_docs, clippy::dbg_macro)] +#![deny( + // rustc lint groups https://doc.rust-lang.org/rustc/lints/groups.html + warnings, + future_incompatible, + let_underscore, + nonstandard_style, + rust_2018_compatibility, + rust_2018_idioms, + rust_2021_compatibility, + unused, + + // rustc allowed-by-default lints https://doc.rust-lang.org/rustc/lints/listing/allowed-by-default.html + macro_use_extern_crate, + meta_variable_misuse, + missing_abi, + missing_copy_implementations, + missing_debug_implementations, + non_ascii_idents, + noop_method_call, + single_use_lifetimes, + trivial_casts, + trivial_numeric_casts, + unreachable_pub, + unsafe_op_in_unsafe_fn, + unused_crate_dependencies, + unused_import_braces, + unused_lifetimes, + unused_qualifications, + unused_tuple_struct_fields, + variant_size_differences, + + // rustdoc lints https://doc.rust-lang.org/rustdoc/lints.html + rustdoc::broken_intra_doc_links, + rustdoc::private_intra_doc_links, + rustdoc::missing_crate_level_docs, + rustdoc::private_doc_tests, + rustdoc::invalid_codeblock_attributes, + rustdoc::invalid_rust_codeblocks, + rustdoc::bare_urls, + + // clippy categories https://doc.rust-lang.org/clippy/ + clippy::all, + clippy::correctness, + clippy::suspicious, + clippy::style, + clippy::complexity, + clippy::perf, + clippy::pedantic, + clippy::nursery, +)] +#![allow( + clippy::module_name_repetitions, + clippy::redundant_pub_crate, + clippy::let_unit_value +)] + +extern crate self as boa_gc; + +mod cell; +mod pointers; +mod trace; + +pub(crate) mod internals; + +use boa_profiler::Profiler; +use internals::GcBox; +use std::{ + cell::{Cell, RefCell}, + mem, + ptr::NonNull, }; + +pub use crate::trace::{Finalize, Trace}; +pub use boa_macros::{Finalize, Trace}; +pub use cell::{GcCell, GcCellRef, GcCellRefMut}; +pub use pointers::{Ephemeron, Gc, WeakGc}; + +type GcPointer = NonNull>; + +thread_local!(static EPHEMERON_QUEUE: Cell>> = Cell::new(None)); +thread_local!(static GC_DROPPING: Cell = Cell::new(false)); +thread_local!(static BOA_GC: RefCell = RefCell::new( BoaGc { + config: GcConfig::default(), + runtime: GcRuntimeData::default(), + adult_start: Cell::new(None), +})); + +#[derive(Debug, Clone, Copy)] +struct GcConfig { + threshold: usize, + used_space_percentage: usize, +} + +// Setting the defaults to an arbitrary value currently. +// +// TODO: Add a configure later +impl Default for GcConfig { + fn default() -> Self { + Self { + threshold: 1024, + used_space_percentage: 80, + } + } +} + +#[derive(Default, Debug, Clone, Copy)] +struct GcRuntimeData { + collections: usize, + bytes_allocated: usize, +} + +#[derive(Debug)] +struct BoaGc { + config: GcConfig, + runtime: GcRuntimeData, + adult_start: Cell>, +} + +impl Drop for BoaGc { + fn drop(&mut self) { + Collector::dump(self); + } +} + +// Whether or not the thread is currently in the sweep phase of garbage collection. +// During this phase, attempts to dereference a `Gc` pointer will trigger a panic. +/// `DropGuard` flags whether the Collector is currently running `Collector::sweep()` or `Collector::dump()` +/// +/// While the `DropGuard` is active, all `GcBox`s must not be dereferenced or accessed as it could cause Undefined Behavior +#[derive(Debug, Clone)] +struct DropGuard; + +impl DropGuard { + fn new() -> Self { + GC_DROPPING.with(|dropping| dropping.set(true)); + Self + } +} + +impl Drop for DropGuard { + fn drop(&mut self) { + GC_DROPPING.with(|dropping| dropping.set(false)); + } +} + +/// Returns `true` if it is safe for a type to run [`Finalize::finalize`]. +#[must_use] +#[inline] +pub fn finalizer_safe() -> bool { + GC_DROPPING.with(|dropping| !dropping.get()) +} + +/// The Allocator handles allocation of garbage collected values. +/// +/// The allocator can trigger a garbage collection. +#[derive(Debug, Clone, Copy)] +struct Allocator; + +impl Allocator { + /// Allocate a new garbage collected value to the Garbage Collector's heap. + fn allocate(value: GcBox) -> NonNull> { + let _timer = Profiler::global().start_event("New Pointer", "BoaAlloc"); + let element_size = mem::size_of_val::>(&value); + BOA_GC.with(|st| { + let mut gc = st.borrow_mut(); + + Self::manage_state(&mut gc); + value.header.next.set(gc.adult_start.take()); + // Safety: Value Cannot be a null as it must be a GcBox + let ptr = unsafe { NonNull::new_unchecked(Box::into_raw(Box::from(value))) }; + + gc.adult_start.set(Some(ptr)); + gc.runtime.bytes_allocated += element_size; + + ptr + }) + } + + fn manage_state(gc: &mut BoaGc) { + if gc.runtime.bytes_allocated > gc.config.threshold { + Collector::run_full_collection(gc); + + if gc.runtime.bytes_allocated + > gc.config.threshold / 100 * gc.config.used_space_percentage + { + gc.config.threshold = + gc.runtime.bytes_allocated / gc.config.used_space_percentage * 100; + } + } + } +} + +// This collector currently functions in four main phases +// +// Mark -> Finalize -> Mark -> Sweep +// +// Mark nodes as reachable then finalize the unreachable nodes. A remark phase +// then needs to be retriggered as finalization can potentially resurrect dead +// nodes. +// +// A better approach in a more concurrent structure may be to reorder. +// +// Mark -> Sweep -> Finalize +struct Collector; + +impl Collector { + /// Run a collection on the full heap. + fn run_full_collection(gc: &mut BoaGc) { + let _timer = Profiler::global().start_event("Gc Full Collection", "gc"); + gc.runtime.collections += 1; + let unreachable_adults = Self::mark_heap(&gc.adult_start); + + // Check if any unreachable nodes were found and finalize + if !unreachable_adults.is_empty() { + // SAFETY: Please see `Collector::finalize()` + unsafe { Self::finalize(unreachable_adults) }; + } + + let _final_unreachable_adults = Self::mark_heap(&gc.adult_start); + + // SAFETY: Please see `Collector::sweep()` + unsafe { + Self::sweep(&gc.adult_start, &mut gc.runtime.bytes_allocated); + } + } + + /// Walk the heap and mark any nodes deemed reachable + fn mark_heap(head: &Cell>>>) -> Vec>> { + let _timer = Profiler::global().start_event("Gc Marking", "gc"); + // Walk the list, tracing and marking the nodes + let mut finalize = Vec::new(); + let mut ephemeron_queue = Vec::new(); + let mut mark_head = head; + while let Some(node) = mark_head.get() { + // SAFETY: node must be valid as it is coming directly from the heap. + let node_ref = unsafe { node.as_ref() }; + if node_ref.header.is_ephemeron() { + ephemeron_queue.push(node); + } else if node_ref.header.roots() > 0 { + // SAFETY: the reference to node must be valid as it is rooted. Passing + // invalid references can result in Undefined Behavior + unsafe { + node_ref.trace_inner(); + } + } else { + finalize.push(node); + } + mark_head = &node_ref.header.next; + } + + // Ephemeron Evaluation + if !ephemeron_queue.is_empty() { + ephemeron_queue = Self::mark_ephemerons(ephemeron_queue); + } + + // Any left over nodes in the ephemeron queue at this point are + // unreachable and need to be notified/finalized. + finalize.extend(ephemeron_queue); + + finalize + } + + // Tracing Ephemerons/Weak is always requires tracing the inner nodes in case it ends up marking unmarked node + // + // Time complexity should be something like O(nd) where d is the longest chain of epehemerons + /// Mark any ephemerons that are deemed live and trace their fields. + fn mark_ephemerons( + initial_queue: Vec>>, + ) -> Vec>> { + let mut ephemeron_queue = initial_queue; + loop { + // iterate through ephemeron queue, sorting nodes by whether they + // are reachable or unreachable + let (reachable, other): (Vec<_>, Vec<_>) = + ephemeron_queue.into_iter().partition(|node| { + // SAFETY: Any node on the eph_queue or the heap must be non null + let node = unsafe { node.as_ref() }; + if node.value.is_marked_ephemeron() { + node.header.mark(); + true + } else { + node.header.roots() > 0 + } + }); + // Replace the old queue with the unreachable + ephemeron_queue = other; + + // If reachable nodes is not empty, trace values. If it is empty, + // break from the loop + if reachable.is_empty() { + break; + } + EPHEMERON_QUEUE.with(|state| state.set(Some(Vec::new()))); + // iterate through reachable nodes and trace their values, + // enqueuing any ephemeron that is found during the trace + for node in reachable { + // TODO: deal with fetch ephemeron_queue + // SAFETY: Node must be a valid pointer or else it would not be deemed reachable. + unsafe { + node.as_ref().weak_trace_inner(); + } + } + + EPHEMERON_QUEUE.with(|st| { + if let Some(found_nodes) = st.take() { + ephemeron_queue.extend(found_nodes); + } + }); + } + ephemeron_queue + } + + /// # Safety + /// + /// Passing a vec with invalid pointers will result in Undefined Behaviour. + unsafe fn finalize(finalize_vec: Vec>>) { + let _timer = Profiler::global().start_event("Gc Finalization", "gc"); + for node in finalize_vec { + // We double check that the unreachable nodes are actually unreachable + // prior to finalization as they could have been marked by a different + // trace after initially being added to the queue + // + // SAFETY: The caller must ensure all pointers inside `finalize_vec` are valid. + let node = unsafe { node.as_ref() }; + if !node.header.is_marked() { + Trace::run_finalizer(&node.value); + } + } + } + + /// # Safety + /// + /// - Providing an invalid pointer in the `heap_start` or in any of the headers of each + /// node will result in Undefined Behaviour. + /// - Providing a list of pointers that weren't allocated by `Box::into_raw(Box::new(..))` + /// will result in Undefined Behaviour. + unsafe fn sweep( + heap_start: &Cell>>>, + total_allocated: &mut usize, + ) { + let _timer = Profiler::global().start_event("Gc Sweeping", "gc"); + let _guard = DropGuard::new(); + + let mut sweep_head = heap_start; + while let Some(node) = sweep_head.get() { + // SAFETY: The caller must ensure the validity of every node of `heap_start`. + let node_ref = unsafe { node.as_ref() }; + if node_ref.is_marked() { + node_ref.header.unmark(); + sweep_head = &node_ref.header.next; + } else if node_ref.header.is_ephemeron() && node_ref.header.roots() > 0 { + // Keep the ephemeron box's alive if rooted, but note that it's pointer is no longer safe + Trace::run_finalizer(&node_ref.value); + sweep_head = &node_ref.header.next; + } else { + // SAFETY: The algorithm ensures only unmarked/unreachable pointers are dropped. + // The caller must ensure all pointers were allocated by `Box::into_raw(Box::new(..))`. + let unmarked_node = unsafe { Box::from_raw(node.as_ptr()) }; + let unallocated_bytes = mem::size_of_val::>(&*unmarked_node); + *total_allocated -= unallocated_bytes; + sweep_head.set(unmarked_node.header.next.take()); + } + } + } + + // Clean up the heap when BoaGc is dropped + fn dump(gc: &mut BoaGc) { + // Not initializing a dropguard since this should only be invoked when BOA_GC is being dropped. + let _guard = DropGuard::new(); + + let sweep_head = &gc.adult_start; + while let Some(node) = sweep_head.get() { + // SAFETY: + // The `Allocator` must always ensure its start node is a valid, non-null pointer that + // was allocated by `Box::from_raw(Box::new(..))`. + let unmarked_node = unsafe { Box::from_raw(node.as_ptr()) }; + sweep_head.set(unmarked_node.header.next.take()); + } + } +} + +/// Forcefully runs a garbage collection of all unaccessible nodes. +pub fn force_collect() { + BOA_GC.with(|current| { + let mut gc = current.borrow_mut(); + + if gc.runtime.bytes_allocated > 0 { + Collector::run_full_collection(&mut gc); + } + }); +} + +#[cfg(test)] +mod test; diff --git a/boa_gc/src/pointers/ephemeron.rs b/boa_gc/src/pointers/ephemeron.rs new file mode 100644 index 00000000000..db21d28c539 --- /dev/null +++ b/boa_gc/src/pointers/ephemeron.rs @@ -0,0 +1,124 @@ +use crate::{ + finalizer_safe, + internals::EphemeronBox, + trace::{Finalize, Trace}, + Allocator, Gc, GcBox, EPHEMERON_QUEUE, +}; +use std::{cell::Cell, ptr::NonNull}; + +#[derive(Debug)] +/// A key-value pair where the value becomes unaccesible when the key is garbage collected. +/// +/// See Racket's explanation on [**ephemerons**][eph] for a brief overview or read Barry Hayes' +/// [_Ephemerons_: a new finalization mechanism][acm]. +/// +/// +/// [eph]: https://docs.racket-lang.org/reference/ephemerons.html +/// [acm]: https://dl.acm.org/doi/10.1145/263700.263733 +pub struct Ephemeron { + inner_ptr: Cell>>>, +} + +impl Ephemeron { + /// Creates a new `Ephemeron`. + pub fn new(key: &Gc, value: V) -> Self { + Self { + inner_ptr: Cell::new(Allocator::allocate(GcBox::new_weak(EphemeronBox::new( + key, value, + )))), + } + } +} + +impl Ephemeron { + #[inline] + fn inner_ptr(&self) -> NonNull>> { + self.inner_ptr.get() + } + + #[inline] + fn inner(&self) -> &GcBox> { + // SAFETY: GcBox> must live until it is unrooted by Drop + unsafe { &*self.inner_ptr().as_ptr() } + } + + #[inline] + /// Gets the weak key of this `Ephemeron`, or `None` if the key was already garbage + /// collected. + pub fn key(&self) -> Option<&K> { + self.inner().value().key() + } + + #[inline] + /// Gets the stored value of this `Ephemeron`. + pub fn value(&self) -> &V { + self.inner().value().value() + } + + #[inline] + /// Gets a `Gc` for the stored key of this `Ephemeron`. + pub fn upgrade_key(&self) -> Option> { + // SAFETY: ptr must be a valid pointer or None would have been returned. + self.inner().value().inner_key_ptr().map(|ptr| unsafe { + let inner_ptr = NonNull::new_unchecked(ptr); + Gc::from_ptr(inner_ptr) + }) + } +} + +impl Finalize for Ephemeron {} + +// SAFETY: Ephemerons trace implementation is standard for everything except `Trace::weak_trace()`, +// which pushes the GcBox> onto the EphemeronQueue +unsafe impl Trace for Ephemeron { + #[inline] + unsafe fn trace(&self) {} + + // Push this Ephemeron's pointer onto the EphemeronQueue + #[inline] + unsafe fn weak_trace(&self) { + EPHEMERON_QUEUE.with(|q| { + let mut queue = q.take().expect("queue is initialized by weak_trace"); + queue.push(self.inner_ptr()); + }); + } + + #[inline] + unsafe fn root(&self) {} + + #[inline] + unsafe fn unroot(&self) {} + + #[inline] + fn run_finalizer(&self) { + Finalize::finalize(self); + } +} + +impl Clone for Ephemeron { + #[inline] + fn clone(&self) -> Self { + // SAFETY: This is safe because the inner_ptr must live as long as it's roots. + // Mismanagement of roots can cause inner_ptr to use after free or Undefined + // Behavior. + unsafe { + let eph = Self { + inner_ptr: Cell::new(NonNull::new_unchecked(self.inner_ptr().as_ptr())), + }; + // Increment the Ephemeron's GcBox roots by 1 + self.inner().root_inner(); + eph + } + } +} + +impl Drop for Ephemeron { + #[inline] + fn drop(&mut self) { + // NOTE: We assert that this drop call is not a + // drop from `Collector::dump` or `Collector::sweep` + if finalizer_safe() { + self.inner().unroot_inner(); + } + } +} diff --git a/boa_gc/src/pointers/gc.rs b/boa_gc/src/pointers/gc.rs new file mode 100644 index 00000000000..2dc9f275fae --- /dev/null +++ b/boa_gc/src/pointers/gc.rs @@ -0,0 +1,279 @@ +use crate::{ + finalizer_safe, + internals::GcBox, + trace::{Finalize, Trace}, + Allocator, +}; +use std::{ + cell::Cell, + cmp::Ordering, + fmt::{self, Debug, Display}, + hash::{Hash, Hasher}, + marker::PhantomData, + ops::Deref, + ptr::{self, addr_of_mut, NonNull}, + rc::Rc, +}; + +// Technically, this function is safe, since we're just modifying the address of a pointer without +// dereferencing it. +pub(crate) fn set_data_ptr(mut ptr: *mut T, data: *mut U) -> *mut T { + // SAFETY: this should be safe as ptr must be a valid nonnull + unsafe { + ptr::write(addr_of_mut!(ptr).cast::<*mut u8>(), data.cast::()); + } + ptr +} + +/// A garbage-collected pointer type over an immutable value. +pub struct Gc { + pub(crate) inner_ptr: Cell>>, + pub(crate) marker: PhantomData>, +} + +impl Gc { + /// Constructs a new `Gc` with the given value. + pub fn new(value: T) -> Self { + // Create GcBox and allocate it to heap. + // + // Note: Allocator can cause Collector to run + let inner_ptr = Allocator::allocate(GcBox::new(value)); + // SAFETY: inner_ptr was just allocated, so it must be a valid value that implements [`Trace`] + unsafe { (*inner_ptr.as_ptr()).value().unroot() } + let gc = Self { + inner_ptr: Cell::new(inner_ptr), + marker: PhantomData, + }; + gc.set_root(); + gc + } +} + +impl Gc { + /// Returns `true` if the two `Gc`s point to the same allocation. + pub fn ptr_eq(this: &Self, other: &Self) -> bool { + GcBox::ptr_eq(this.inner(), other.inner()) + } + + /// Will return a new rooted `Gc` from a `GcBox` pointer + pub(crate) fn from_ptr(ptr: NonNull>) -> Self { + // SAFETY: the value provided as a pointer MUST be a valid GcBox. + unsafe { + ptr.as_ref().root_inner(); + let gc = Self { + inner_ptr: Cell::new(ptr), + marker: PhantomData, + }; + gc.set_root(); + gc + } + } +} + +/// Returns the given pointer with its root bit cleared. +pub(crate) unsafe fn clear_root_bit( + ptr: NonNull>, +) -> NonNull> { + let ptr = ptr.as_ptr(); + let data = ptr.cast::(); + let addr = data as isize; + let ptr = set_data_ptr(ptr, data.wrapping_offset((addr & !1) - addr)); + // SAFETY: ptr must be a non null value + unsafe { NonNull::new_unchecked(ptr) } +} + +impl Gc { + fn rooted(&self) -> bool { + self.inner_ptr.get().as_ptr().cast::() as usize & 1 != 0 + } + + pub(crate) fn set_root(&self) { + let ptr = self.inner_ptr.get().as_ptr(); + let data = ptr.cast::(); + let addr = data as isize; + let ptr = set_data_ptr(ptr, data.wrapping_offset((addr | 1) - addr)); + // SAFETY: ptr must be a non null value. + unsafe { + self.inner_ptr.set(NonNull::new_unchecked(ptr)); + } + } + + fn clear_root(&self) { + // SAFETY: inner_ptr must be a valid non-null pointer to a live GcBox. + unsafe { + self.inner_ptr.set(clear_root_bit(self.inner_ptr.get())); + } + } + + #[inline] + pub(crate) fn inner_ptr(&self) -> NonNull> { + assert!(finalizer_safe()); + // SAFETY: inner_ptr must be a live GcBox. Calling this on a dropped GcBox + // can result in Undefined Behavior. + unsafe { clear_root_bit(self.inner_ptr.get()) } + } + + #[inline] + fn inner(&self) -> &GcBox { + // SAFETY: Please see Gc::inner_ptr() + unsafe { self.inner_ptr().as_ref() } + } +} + +impl Finalize for Gc {} + +// SAFETY: `Gc` maintains it's own rootedness and implements all methods of +// Trace. It is not possible to root an already rooted `Gc` and vice versa. +unsafe impl Trace for Gc { + #[inline] + unsafe fn trace(&self) { + // SAFETY: Inner must be live and allocated GcBox. + unsafe { + self.inner().trace_inner(); + } + } + + #[inline] + unsafe fn weak_trace(&self) { + self.inner().weak_trace_inner(); + } + + #[inline] + unsafe fn root(&self) { + assert!(!self.rooted(), "Can't double-root a Gc"); + // Try to get inner before modifying our state. Inner may be + // inaccessible due to this method being invoked during the sweeping + // phase, and we don't want to modify our state before panicking. + self.inner().root_inner(); + self.set_root(); + } + + #[inline] + unsafe fn unroot(&self) { + assert!(self.rooted(), "Can't double-unroot a Gc"); + // Try to get inner before modifying our state. Inner may be + // inaccessible due to this method being invoked during the sweeping + // phase, and we don't want to modify our state before panicking. + self.inner().unroot_inner(); + self.clear_root(); + } + + #[inline] + fn run_finalizer(&self) { + Finalize::finalize(self); + } +} + +impl Clone for Gc { + #[inline] + fn clone(&self) -> Self { + Self::from_ptr(self.inner_ptr()) + } +} + +impl Deref for Gc { + type Target = T; + + #[inline] + fn deref(&self) -> &T { + self.inner().value() + } +} + +impl Drop for Gc { + #[inline] + fn drop(&mut self) { + // If this pointer was a root, we should unroot it. + if self.rooted() { + self.inner().unroot_inner(); + } + } +} + +impl Default for Gc { + #[inline] + fn default() -> Self { + Self::new(Default::default()) + } +} + +#[allow(clippy::inline_always)] +impl PartialEq for Gc { + #[inline(always)] + fn eq(&self, other: &Self) -> bool { + **self == **other + } +} + +impl Eq for Gc {} + +#[allow(clippy::inline_always)] +impl PartialOrd for Gc { + #[inline(always)] + fn partial_cmp(&self, other: &Self) -> Option { + (**self).partial_cmp(&**other) + } + + #[inline(always)] + fn lt(&self, other: &Self) -> bool { + **self < **other + } + + #[inline(always)] + fn le(&self, other: &Self) -> bool { + **self <= **other + } + + #[inline(always)] + fn gt(&self, other: &Self) -> bool { + **self > **other + } + + #[inline(always)] + fn ge(&self, other: &Self) -> bool { + **self >= **other + } +} + +impl Ord for Gc { + #[inline] + fn cmp(&self, other: &Self) -> Ordering { + (**self).cmp(&**other) + } +} + +impl Hash for Gc { + fn hash(&self, state: &mut H) { + (**self).hash(state); + } +} + +impl Display for Gc { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + Display::fmt(&**self, f) + } +} + +impl Debug for Gc { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + Debug::fmt(&**self, f) + } +} + +impl fmt::Pointer for Gc { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Pointer::fmt(&self.inner(), f) + } +} + +impl std::borrow::Borrow for Gc { + fn borrow(&self) -> &T { + self + } +} + +impl AsRef for Gc { + fn as_ref(&self) -> &T { + self + } +} diff --git a/boa_gc/src/pointers/mod.rs b/boa_gc/src/pointers/mod.rs new file mode 100644 index 00000000000..5c14182762d --- /dev/null +++ b/boa_gc/src/pointers/mod.rs @@ -0,0 +1,9 @@ +//! Pointers represents the External types returned by the Boa Garbage Collector + +mod ephemeron; +mod gc; +mod weak; + +pub use ephemeron::Ephemeron; +pub use gc::Gc; +pub use weak::WeakGc; diff --git a/boa_gc/src/pointers/weak.rs b/boa_gc/src/pointers/weak.rs new file mode 100644 index 00000000000..26a1945efa2 --- /dev/null +++ b/boa_gc/src/pointers/weak.rs @@ -0,0 +1,49 @@ +use crate::{Ephemeron, Finalize, Gc, Trace}; + +/// A weak reference to a [`Gc`]. +/// +/// This type allows keeping references to [`Gc`] managed values without keeping them alive for +/// garbage collections. However, this also means [`WeakGc::value`] can return `None` at any moment. +#[derive(Debug, Trace, Finalize)] +#[repr(transparent)] +pub struct WeakGc { + inner: Ephemeron, +} + +impl WeakGc { + /// Creates a new weak pointer for a garbage collected value. + pub fn new(value: &Gc) -> Self { + Self { + inner: Ephemeron::new(value, ()), + } + } +} + +impl WeakGc { + #[inline] + /// Gets the value of this weak pointer, or `None` if the value was already garbage collected. + pub fn value(&self) -> Option<&T> { + self.inner.key() + } + + #[inline] + /// Upgrade returns a `Gc` pointer for the internal value if valid, or None if the value was already garbage collected. + pub fn upgrade(&self) -> Option> { + self.inner.upgrade_key() + } +} + +impl Clone for WeakGc { + #[inline] + fn clone(&self) -> Self { + Self { + inner: self.inner.clone(), + } + } +} + +impl From> for WeakGc { + fn from(inner: Ephemeron) -> Self { + Self { inner } + } +} diff --git a/boa_gc/src/test/allocation.rs b/boa_gc/src/test/allocation.rs new file mode 100644 index 00000000000..27386999836 --- /dev/null +++ b/boa_gc/src/test/allocation.rs @@ -0,0 +1,31 @@ +use super::{run_test, Harness}; +use crate::{force_collect, Gc, GcCell}; + +#[test] +fn gc_basic_cell_allocation() { + run_test(|| { + let gc_cell = Gc::new(GcCell::new(16_u16)); + + force_collect(); + Harness::assert_collections(1); + Harness::assert_bytes_allocated(); + assert_eq!(*gc_cell.borrow_mut(), 16); + }); +} + +#[test] +fn gc_basic_pointer_alloc() { + run_test(|| { + let gc = Gc::new(16_u8); + + force_collect(); + Harness::assert_collections(1); + Harness::assert_bytes_allocated(); + assert_eq!(*gc, 16); + + drop(gc); + force_collect(); + Harness::assert_collections(2); + Harness::assert_empty_gc(); + }); +} diff --git a/boa_gc/src/test/cell.rs b/boa_gc/src/test/cell.rs new file mode 100644 index 00000000000..3c067fd3cb6 --- /dev/null +++ b/boa_gc/src/test/cell.rs @@ -0,0 +1,14 @@ +use super::run_test; +use crate::{Gc, GcCell}; + +#[test] +fn boa_borrow_mut_test() { + run_test(|| { + let v = Gc::new(GcCell::new(Vec::new())); + + for _ in 1..=259 { + let cell = Gc::new(GcCell::new([0u8; 10])); + v.borrow_mut().push(cell); + } + }); +} diff --git a/boa_gc/src/test/mod.rs b/boa_gc/src/test/mod.rs new file mode 100644 index 00000000000..559fbb8d80f --- /dev/null +++ b/boa_gc/src/test/mod.rs @@ -0,0 +1,37 @@ +use crate::BOA_GC; + +mod allocation; +mod cell; +mod weak; + +struct Harness; + +impl Harness { + fn assert_collections(o: usize) { + BOA_GC.with(|current| { + let gc = current.borrow(); + assert_eq!(gc.runtime.collections, o); + }); + } + + fn assert_empty_gc() { + BOA_GC.with(|current| { + let gc = current.borrow(); + + assert!(gc.adult_start.get().is_none()); + assert!(gc.runtime.bytes_allocated == 0); + }); + } + + fn assert_bytes_allocated() { + BOA_GC.with(|current| { + let gc = current.borrow(); + assert!(gc.runtime.bytes_allocated > 0); + }); + } +} + +fn run_test(test: impl FnOnce() + Send + 'static) { + let handle = std::thread::spawn(test); + handle.join().unwrap(); +} diff --git a/boa_gc/src/test/weak.rs b/boa_gc/src/test/weak.rs new file mode 100644 index 00000000000..709c03edbb5 --- /dev/null +++ b/boa_gc/src/test/weak.rs @@ -0,0 +1,132 @@ +use super::run_test; +use crate::{force_collect, Ephemeron, Gc, WeakGc}; + +#[test] +fn eph_weak_gc_test() { + run_test(|| { + let gc_value = Gc::new(3); + + { + let cloned_gc = gc_value.clone(); + + let weak = WeakGc::new(&cloned_gc); + + assert_eq!(*weak.value().expect("Is live currently"), 3); + drop(cloned_gc); + force_collect(); + assert_eq!(*weak.value().expect("WeakGc is still live here"), 3); + + drop(gc_value); + force_collect(); + + assert!(weak.value().is_none()); + } + }); +} + +#[test] +fn eph_ephemeron_test() { + run_test(|| { + let gc_value = Gc::new(3); + + { + let cloned_gc = gc_value.clone(); + + let ephemeron = Ephemeron::new(&cloned_gc, String::from("Hello World!")); + + assert_eq!(*ephemeron.key().expect("Ephemeron is live"), 3); + assert_eq!(*ephemeron.value(), String::from("Hello World!")); + drop(cloned_gc); + force_collect(); + assert_eq!(*ephemeron.key().expect("Ephemeron is still live here"), 3); + + drop(gc_value); + force_collect(); + + assert!(ephemeron.key().is_none()); + } + }); +} + +#[test] +fn eph_allocation_chains() { + run_test(|| { + let gc_value = Gc::new(String::from("foo")); + + { + let cloned_gc = gc_value.clone(); + let weak = WeakGc::new(&cloned_gc); + let wrap = Gc::new(weak); + + assert_eq!(wrap.value().expect("weak is live"), &String::from("foo")); + + let eph = Ephemeron::new(&wrap, 3); + + drop(cloned_gc); + force_collect(); + + assert_eq!( + eph.key() + .expect("eph is still live") + .value() + .expect("weak is still live"), + &String::from("foo") + ); + + drop(gc_value); + force_collect(); + + assert!(eph.key().expect("eph is still live").value().is_none()); + } + }); +} + +#[test] +fn eph_basic_alloc_dump_test() { + run_test(|| { + let gc_value = Gc::new(String::from("gc here")); + let _gc_two = Gc::new("hmmm"); + + let eph = Ephemeron::new(&gc_value, 4); + let _fourth = Gc::new("tail"); + + assert_eq!(*eph.key().expect("must be live"), String::from("gc here")); + }); +} + +#[test] +fn eph_basic_upgrade_test() { + run_test(|| { + let init_gc = Gc::new(String::from("foo")); + + let weak = WeakGc::new(&init_gc); + + let new_gc = weak.upgrade().expect("Weak is still live"); + + drop(weak); + force_collect(); + + assert_eq!(*init_gc, *new_gc); + }); +} + +#[test] +fn eph_basic_clone_test() { + run_test(|| { + let init_gc = Gc::new(String::from("bar")); + + let weak = WeakGc::new(&init_gc); + + let new_gc = weak.upgrade().expect("Weak is live"); + let new_weak = weak.clone(); + + drop(weak); + force_collect(); + + assert_eq!(*new_gc, *new_weak.value().expect("weak should be live")); + assert_eq!( + *init_gc, + *new_weak.value().expect("weak_should be live still") + ); + }); +} diff --git a/boa_gc/src/trace.rs b/boa_gc/src/trace.rs new file mode 100644 index 00000000000..06512fcaa7c --- /dev/null +++ b/boa_gc/src/trace.rs @@ -0,0 +1,452 @@ +use std::{ + borrow::{Cow, ToOwned}, + collections::{BTreeMap, BTreeSet, BinaryHeap, HashMap, HashSet, LinkedList, VecDeque}, + hash::{BuildHasher, Hash}, + marker::PhantomData, + num::{ + NonZeroI128, NonZeroI16, NonZeroI32, NonZeroI64, NonZeroI8, NonZeroIsize, NonZeroU128, + NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU8, NonZeroUsize, + }, + path::{Path, PathBuf}, + rc::Rc, + sync::atomic::{ + AtomicBool, AtomicI16, AtomicI32, AtomicI64, AtomicI8, AtomicIsize, AtomicU16, AtomicU32, + AtomicU64, AtomicU8, AtomicUsize, + }, +}; + +/// Substitute for the [`Drop`] trait for garbage collected types. +pub trait Finalize { + /// Cleanup logic for a type. + fn finalize(&self) {} +} + +/// The Trace trait, which needs to be implemented on garbage-collected objects. +/// +/// # Safety +/// +/// - An incorrect implementation of the trait can result in heap overflows, data corruption, +/// use-after-free, or Undefined Behaviour in general. +/// +/// - Calling any of the functions marked as `unsafe` outside of the context of the garbage collector +/// can result in Undefined Behaviour. +pub unsafe trait Trace: Finalize { + /// Marks all contained `Gc`s. + /// + /// # Safety + /// + /// See [`Trace`]. + unsafe fn trace(&self); + + /// Marks all contained weak references of a `Gc`. + /// + /// # Safety + /// + /// See [`Trace`]. + unsafe fn weak_trace(&self); + + /// Increments the root-count of all contained `Gc`s. + /// + /// # Safety + /// + /// See [`Trace`]. + unsafe fn root(&self); + + /// Decrements the root-count of all contained `Gc`s. + /// + /// # Safety + /// + /// See [`Trace`]. + unsafe fn unroot(&self); + + /// Checks if an ephemeron's key is marked. + #[doc(hidden)] + fn is_marked_ephemeron(&self) -> bool { + false + } + + /// Runs [`Finalize::finalize`] on this object and all + /// contained subobjects. + fn run_finalizer(&self); +} + +/// Utility macro to define an empty implementation of [`Trace`]. +/// +/// Use this for marking types as not containing any `Trace` types. +#[macro_export] +macro_rules! empty_trace { + () => { + #[inline] + unsafe fn trace(&self) {} + #[inline] + unsafe fn weak_trace(&self) {} + #[inline] + unsafe fn root(&self) {} + #[inline] + unsafe fn unroot(&self) {} + #[inline] + fn run_finalizer(&self) { + $crate::Finalize::finalize(self) + } + }; +} + +/// Utility macro to manually implement [`Trace`] on a type. +/// +/// You define a `this` parameter name and pass in a body, which should call `mark` on every +/// traceable element inside the body. The mark implementation will automatically delegate to the +/// correct method on the argument. +/// +/// # Safety +/// +/// Misusing the `mark` function may result in Undefined Behaviour. +#[macro_export] +macro_rules! custom_trace { + ($this:ident, $body:expr) => { + #[inline] + unsafe fn trace(&self) { + #[inline] + fn mark(it: &T) { + // SAFETY: The implementor must ensure that `trace` is correctly implemented. + unsafe { + $crate::Trace::trace(it); + } + } + let $this = self; + $body + } + #[inline] + unsafe fn weak_trace(&self) { + #[inline] + fn mark(it: &T) { + // SAFETY: The implementor must ensure that `weak_trace` is correctly implemented. + unsafe { + $crate::Trace::weak_trace(it); + } + } + let $this = self; + $body + } + #[inline] + unsafe fn root(&self) { + #[inline] + fn mark(it: &T) { + // SAFETY: The implementor must ensure that `root` is correctly implemented. + unsafe { + $crate::Trace::root(it); + } + } + let $this = self; + $body + } + #[inline] + unsafe fn unroot(&self) { + #[inline] + fn mark(it: &T) { + // SAFETY: The implementor must ensure that `unroot` is correctly implemented. + unsafe { + $crate::Trace::unroot(it); + } + } + let $this = self; + $body + } + #[inline] + fn run_finalizer(&self) { + #[inline] + fn mark(it: &T) { + $crate::Trace::run_finalizer(it); + } + $crate::Finalize::finalize(self); + let $this = self; + $body + } + }; +} + +impl Finalize for &'static T {} +// SAFETY: 'static references don't need to be traced, since they live indefinitely. +unsafe impl Trace for &'static T { + empty_trace!(); +} + +macro_rules! simple_empty_finalize_trace { + ($($T:ty),*) => { + $( + impl Finalize for $T {} + + // SAFETY: + // Primitive types and string types don't have inner nodes that need to be marked. + unsafe impl Trace for $T { empty_trace!(); } + )* + } +} + +simple_empty_finalize_trace![ + (), + bool, + isize, + usize, + i8, + u8, + i16, + u16, + i32, + u32, + i64, + u64, + i128, + u128, + f32, + f64, + char, + String, + Box, + Rc, + Path, + PathBuf, + NonZeroIsize, + NonZeroUsize, + NonZeroI8, + NonZeroU8, + NonZeroI16, + NonZeroU16, + NonZeroI32, + NonZeroU32, + NonZeroI64, + NonZeroU64, + NonZeroI128, + NonZeroU128, + AtomicBool, + AtomicIsize, + AtomicUsize, + AtomicI8, + AtomicU8, + AtomicI16, + AtomicU16, + AtomicI32, + AtomicU32, + AtomicI64, + AtomicU64 +]; + +impl Finalize for [T; N] {} +// SAFETY: +// All elements inside the array are correctly marked. +unsafe impl Trace for [T; N] { + custom_trace!(this, { + for v in this { + mark(v); + } + }); +} + +macro_rules! fn_finalize_trace_one { + ($ty:ty $(,$args:ident)*) => { + impl Finalize for $ty {} + // SAFETY: + // Function pointers don't have inner nodes that need to be marked. + unsafe impl Trace for $ty { empty_trace!(); } + } +} +macro_rules! fn_finalize_trace_group { + () => { + fn_finalize_trace_one!(extern "Rust" fn () -> Ret); + fn_finalize_trace_one!(extern "C" fn () -> Ret); + fn_finalize_trace_one!(unsafe extern "Rust" fn () -> Ret); + fn_finalize_trace_one!(unsafe extern "C" fn () -> Ret); + }; + ($($args:ident),*) => { + fn_finalize_trace_one!(extern "Rust" fn ($($args),*) -> Ret, $($args),*); + fn_finalize_trace_one!(extern "C" fn ($($args),*) -> Ret, $($args),*); + fn_finalize_trace_one!(extern "C" fn ($($args),*, ...) -> Ret, $($args),*); + fn_finalize_trace_one!(unsafe extern "Rust" fn ($($args),*) -> Ret, $($args),*); + fn_finalize_trace_one!(unsafe extern "C" fn ($($args),*) -> Ret, $($args),*); + fn_finalize_trace_one!(unsafe extern "C" fn ($($args),*, ...) -> Ret, $($args),*); + } +} + +macro_rules! tuple_finalize_trace { + () => {}; // This case is handled above, by simple_finalize_empty_trace!(). + ($($args:ident),*) => { + impl<$($args),*> Finalize for ($($args,)*) {} + // SAFETY: + // All elements inside the tuple are correctly marked. + unsafe impl<$($args: $crate::Trace),*> Trace for ($($args,)*) { + custom_trace!(this, { + #[allow(non_snake_case, unused_unsafe)] + fn avoid_lints<$($args: $crate::Trace),*>(&($(ref $args,)*): &($($args,)*)) { + // SAFETY: The implementor must ensure a correct implementation. + unsafe { $(mark($args);)* } + } + avoid_lints(this) + }); + } + } +} + +macro_rules! type_arg_tuple_based_finalize_trace_impls { + ($(($($args:ident),*);)*) => { + $( + fn_finalize_trace_group!($($args),*); + tuple_finalize_trace!($($args),*); + )* + } +} + +type_arg_tuple_based_finalize_trace_impls![ + (); + (A); + (A, B); + (A, B, C); + (A, B, C, D); + (A, B, C, D, E); + (A, B, C, D, E, F); + (A, B, C, D, E, F, G); + (A, B, C, D, E, F, G, H); + (A, B, C, D, E, F, G, H, I); + (A, B, C, D, E, F, G, H, I, J); + (A, B, C, D, E, F, G, H, I, J, K); + (A, B, C, D, E, F, G, H, I, J, K, L); +]; + +impl Finalize for Box {} +// SAFETY: The inner value of the `Box` is correctly marked. +unsafe impl Trace for Box { + custom_trace!(this, { + mark(&**this); + }); +} + +impl Finalize for Box<[T]> {} +// SAFETY: All the inner elements of the `Box` array are correctly marked. +unsafe impl Trace for Box<[T]> { + custom_trace!(this, { + for e in this.iter() { + mark(e); + } + }); +} + +impl Finalize for Vec {} +// SAFETY: All the inner elements of the `Vec` are correctly marked. +unsafe impl Trace for Vec { + custom_trace!(this, { + for e in this { + mark(e); + } + }); +} + +impl Finalize for Option {} +// SAFETY: The inner value of the `Option` is correctly marked. +unsafe impl Trace for Option { + custom_trace!(this, { + if let Some(ref v) = *this { + mark(v); + } + }); +} + +impl Finalize for Result {} +// SAFETY: Both inner values of the `Result` are correctly marked. +unsafe impl Trace for Result { + custom_trace!(this, { + match *this { + Ok(ref v) => mark(v), + Err(ref v) => mark(v), + } + }); +} + +impl Finalize for BinaryHeap {} +// SAFETY: All the elements of the `BinaryHeap` are correctly marked. +unsafe impl Trace for BinaryHeap { + custom_trace!(this, { + for v in this.iter() { + mark(v); + } + }); +} + +impl Finalize for BTreeMap {} +// SAFETY: All the elements of the `BTreeMap` are correctly marked. +unsafe impl Trace for BTreeMap { + custom_trace!(this, { + for (k, v) in this { + mark(k); + mark(v); + } + }); +} + +impl Finalize for BTreeSet {} +// SAFETY: All the elements of the `BTreeSet` are correctly marked. +unsafe impl Trace for BTreeSet { + custom_trace!(this, { + for v in this { + mark(v); + } + }); +} + +impl Finalize for HashMap {} +// SAFETY: All the elements of the `HashMap` are correctly marked. +unsafe impl Trace for HashMap { + custom_trace!(this, { + for (k, v) in this.iter() { + mark(k); + mark(v); + } + }); +} + +impl Finalize for HashSet {} +// SAFETY: All the elements of the `HashSet` are correctly marked. +unsafe impl Trace for HashSet { + custom_trace!(this, { + for v in this.iter() { + mark(v); + } + }); +} + +impl Finalize for LinkedList {} +// SAFETY: All the elements of the `LinkedList` are correctly marked. +unsafe impl Trace for LinkedList { + custom_trace!(this, { + for v in this.iter() { + mark(v); + } + }); +} + +impl Finalize for PhantomData {} +// SAFETY: A `PhantomData` doesn't have inner data that needs to be marked. +unsafe impl Trace for PhantomData { + empty_trace!(); +} + +impl Finalize for VecDeque {} +// SAFETY: All the elements of the `VecDeque` are correctly marked. +unsafe impl Trace for VecDeque { + custom_trace!(this, { + for v in this.iter() { + mark(v); + } + }); +} + +impl Finalize for Cow<'static, T> {} +// SAFETY: 'static references don't need to be traced, since they live indefinitely, and the owned +// variant is correctly marked. +unsafe impl Trace for Cow<'static, T> +where + T::Owned: Trace, +{ + custom_trace!(this, { + if let Cow::Owned(ref v) = this { + mark(v); + } + }); +} diff --git a/boa_interner/Cargo.toml b/boa_interner/Cargo.toml index 5f07b9289d3..269d712c37a 100644 --- a/boa_interner/Cargo.toml +++ b/boa_interner/Cargo.toml @@ -20,5 +20,5 @@ phf = { version = "0.11.1", features = ["macros"] } rustc-hash = "1.1.0" static_assertions = "1.1.0" once_cell = "1.16.0" -indexmap = "1.9.1" +indexmap = "1.9.2" arbitrary = { version = "1", optional = true, features = ["derive"] } diff --git a/boa_interner/src/interned_str.rs b/boa_interner/src/interned_str.rs index 7f5654a22cf..e534914ff56 100644 --- a/boa_interner/src/interned_str.rs +++ b/boa_interner/src/interned_str.rs @@ -27,7 +27,7 @@ impl InternedStr { /// Not maintaining the invariants specified on the struct definition /// could cause Undefined Behaviour. #[inline] - pub(super) unsafe fn new(ptr: NonNull<[Char]>) -> Self { + pub(super) const unsafe fn new(ptr: NonNull<[Char]>) -> Self { Self { ptr } } diff --git a/boa_interner/src/lib.rs b/boa_interner/src/lib.rs index 112b3cd7e71..ebcdd4d7d92 100644 --- a/boa_interner/src/lib.rs +++ b/boa_interner/src/lib.rs @@ -13,62 +13,60 @@ html_favicon_url = "https://raw.githubusercontent.com/boa-dev/boa/main/assets/logo.svg" )] #![cfg_attr(not(test), forbid(clippy::unwrap_used))] -#![warn( - clippy::perf, - clippy::single_match_else, - clippy::dbg_macro, - clippy::doc_markdown, - clippy::wildcard_imports, - clippy::struct_excessive_bools, - clippy::doc_markdown, - clippy::semicolon_if_nothing_returned, - clippy::pedantic -)] +#![warn(missing_docs, clippy::dbg_macro)] #![deny( - clippy::all, - clippy::cast_lossless, - clippy::redundant_closure_for_method_calls, - clippy::use_self, - clippy::unnested_or_patterns, - clippy::trivially_copy_pass_by_ref, - clippy::needless_pass_by_value, - clippy::match_wildcard_for_single_variants, - clippy::map_unwrap_or, - unused_qualifications, - unused_import_braces, - unused_lifetimes, - unreachable_pub, - trivial_numeric_casts, - // rustdoc, - missing_debug_implementations, - missing_copy_implementations, - deprecated_in_future, - meta_variable_misuse, - non_ascii_idents, - rust_2018_compatibility, - rust_2018_idioms, + // rustc lint groups https://doc.rust-lang.org/rustc/lints/groups.html + warnings, future_incompatible, + let_underscore, nonstandard_style, - unsafe_op_in_unsafe_fn + rust_2018_compatibility, + rust_2018_idioms, + rust_2021_compatibility, + unused, + + // rustc allowed-by-default lints https://doc.rust-lang.org/rustc/lints/listing/allowed-by-default.html + macro_use_extern_crate, + meta_variable_misuse, + missing_abi, + missing_copy_implementations, + missing_debug_implementations, + non_ascii_idents, + noop_method_call, + trivial_casts, + trivial_numeric_casts, + unreachable_pub, + unsafe_op_in_unsafe_fn, + unused_crate_dependencies, + unused_import_braces, + unused_lifetimes, + unused_qualifications, + unused_tuple_struct_fields, + variant_size_differences, + + // rustdoc lints https://doc.rust-lang.org/rustdoc/lints.html + rustdoc::broken_intra_doc_links, + rustdoc::private_intra_doc_links, + rustdoc::missing_crate_level_docs, + rustdoc::private_doc_tests, + rustdoc::invalid_codeblock_attributes, + rustdoc::invalid_rust_codeblocks, + rustdoc::bare_urls, + + // clippy categories https://doc.rust-lang.org/clippy/ + clippy::all, + clippy::correctness, + clippy::suspicious, + clippy::style, + clippy::complexity, + clippy::perf, + clippy::pedantic, + clippy::nursery, )] #![allow( - clippy::module_name_repetitions, - clippy::cast_possible_truncation, - clippy::cast_sign_loss, - clippy::cast_precision_loss, - clippy::cast_possible_wrap, - clippy::cast_ptr_alignment, - clippy::missing_panics_doc, - clippy::too_many_lines, - clippy::unreadable_literal, - clippy::missing_inline_in_public_items, - clippy::cognitive_complexity, - clippy::must_use_candidate, - clippy::missing_errors_doc, - clippy::as_conversions, - clippy::let_unit_value, + clippy::redundant_pub_crate, // TODO deny once false positive is fixed (https://github.com/rust-lang/rust-clippy/issues/9626). - clippy::trait_duplication_in_bounds, + clippy::trait_duplication_in_bounds )] extern crate static_assertions as sa; @@ -77,12 +75,13 @@ mod fixed_string; mod interned_str; mod raw; mod sym; + #[cfg(test)] mod tests; +use raw::RawInterner; use std::borrow::Cow; -use raw::RawInterner; pub use sym::*; /// An enumeration of all slice types [`Interner`] can internally store. @@ -91,7 +90,10 @@ pub use sym::*; /// encodings [`Interner`] can store. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum JStrRef<'a> { + /// A `UTF-8` string reference. Utf8(&'a str), + + /// A `UTF-16` string reference. Utf16(&'a [u16]), } @@ -128,12 +130,16 @@ pub struct JSInternedStrRef<'a, 'b> { impl<'a, 'b> JSInternedStrRef<'a, 'b> { /// Returns the inner reference to the interned string in `UTF-8` encoding. /// if the string is not representable in `UTF-8`, returns [`None`] - pub fn utf8(&self) -> Option<&'a str> { + #[inline] + #[must_use] + pub const fn utf8(&self) -> Option<&'a str> { self.utf8 } /// Returns the inner reference to the interned string in `UTF-16` encoding. - pub fn utf16(&self) -> &'b [u16] { + #[inline] + #[must_use] + pub const fn utf16(&self) -> &'b [u16] { self.utf16 } @@ -186,7 +192,7 @@ impl<'a, 'b> JSInternedStrRef<'a, 'b> { } } -impl<'a, 'b> std::fmt::Display for JSInternedStrRef<'a, 'b> { +impl std::fmt::Display for JSInternedStrRef<'_, '_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.join_with_context( std::fmt::Display::fmt, @@ -215,12 +221,14 @@ pub struct Interner { impl Interner { /// Creates a new [`Interner`]. #[inline] + #[must_use] pub fn new() -> Self { Self::default() } /// Creates a new [`Interner`] with the specified capacity. #[inline] + #[must_use] pub fn with_capacity(capacity: usize) -> Self { Self { utf8_interner: RawInterner::with_capacity(capacity), @@ -230,6 +238,7 @@ impl Interner { /// Returns the number of strings interned by the interner. #[inline] + #[must_use] pub fn len(&self) -> usize { // `utf16_interner.len()` == `utf8_interner.len()`, // so we can use any of them. @@ -238,6 +247,7 @@ impl Interner { /// Returns `true` if the [`Interner`] contains no interned strings. #[inline] + #[must_use] pub fn is_empty(&self) -> bool { COMMON_STRINGS_UTF8.is_empty() && self.utf16_interner.is_empty() } @@ -342,6 +352,7 @@ impl Interner { /// Returns the string for the given symbol if any. #[inline] + #[must_use] pub fn resolve(&self, symbol: Sym) -> Option> { let index = symbol.get() - 1; @@ -385,6 +396,7 @@ impl Interner { /// /// If the interner cannot resolve the given symbol. #[inline] + #[must_use] pub fn resolve_expect(&self, symbol: Sym) -> JSInternedStrRef<'_, '_> { self.resolve(symbol).expect("string disappeared") } diff --git a/boa_interner/src/raw.rs b/boa_interner/src/raw.rs index 9604f9bff77..2fed0be3999 100644 --- a/boa_interner/src/raw.rs +++ b/boa_interner/src/raw.rs @@ -1,8 +1,6 @@ -use std::hash::Hash; - -use rustc_hash::FxHashMap; - use crate::{fixed_string::FixedString, interned_str::InternedStr}; +use rustc_hash::FxHashMap; +use std::hash::Hash; /// Raw string interner, generic by a char type. #[derive(Debug)] diff --git a/boa_interner/src/sym.rs b/boa_interner/src/sym.rs index 8cf8385bef0..32114070f58 100644 --- a/boa_interner/src/sym.rs +++ b/boa_interner/src/sym.rs @@ -131,6 +131,7 @@ impl Sym { /// Returns the internal value of the [`Sym`] #[inline] + #[must_use] pub const fn get(self) -> usize { self.value.get() } diff --git a/boa_macros/Cargo.toml b/boa_macros/Cargo.toml index d68aae7e972..048fd0f0d67 100644 --- a/boa_macros/Cargo.toml +++ b/boa_macros/Cargo.toml @@ -15,4 +15,5 @@ proc-macro = true [dependencies] quote = "1.0.21" syn = "1.0.103" - +proc-macro2 = "1.0" +synstructure = "0.12" diff --git a/boa_macros/src/lib.rs b/boa_macros/src/lib.rs index 205905786c9..6525e7665c5 100644 --- a/boa_macros/src/lib.rs +++ b/boa_macros/src/lib.rs @@ -1,6 +1,62 @@ +//! Macros for the Boa JavaScript engine. + +#![cfg_attr(not(test), forbid(clippy::unwrap_used))] +#![warn(missing_docs, clippy::dbg_macro)] +#![deny( + // rustc lint groups https://doc.rust-lang.org/rustc/lints/groups.html + warnings, + future_incompatible, + let_underscore, + nonstandard_style, + rust_2018_compatibility, + rust_2018_idioms, + rust_2021_compatibility, + unused, + + // rustc allowed-by-default lints https://doc.rust-lang.org/rustc/lints/listing/allowed-by-default.html + macro_use_extern_crate, + meta_variable_misuse, + missing_abi, + missing_copy_implementations, + missing_debug_implementations, + non_ascii_idents, + noop_method_call, + single_use_lifetimes, + trivial_casts, + trivial_numeric_casts, + unreachable_pub, + unsafe_op_in_unsafe_fn, + unused_crate_dependencies, + unused_import_braces, + unused_lifetimes, + unused_qualifications, + unused_tuple_struct_fields, + variant_size_differences, + + // rustdoc lints https://doc.rust-lang.org/rustdoc/lints.html + rustdoc::broken_intra_doc_links, + rustdoc::private_intra_doc_links, + rustdoc::missing_crate_level_docs, + rustdoc::private_doc_tests, + rustdoc::invalid_codeblock_attributes, + rustdoc::invalid_rust_codeblocks, + rustdoc::bare_urls, + + // clippy categories https://doc.rust-lang.org/clippy/ + clippy::all, + clippy::correctness, + clippy::suspicious, + clippy::style, + clippy::complexity, + clippy::perf, + clippy::pedantic, + clippy::nursery, +)] + use proc_macro::TokenStream; use quote::quote; use syn::{parse_macro_input, LitStr}; +use synstructure::{decl_derive, AddBounds, Structure}; /// Construct a utf-16 array literal from a utf-8 [`str`] literal. #[proc_macro] @@ -13,3 +69,112 @@ pub fn utf16(input: TokenStream) -> TokenStream { } .into() } + +decl_derive! { + [Trace, attributes(unsafe_ignore_trace)] => + /// Derive the Trace trait. + derive_trace +} + +fn derive_trace(mut s: Structure<'_>) -> proc_macro2::TokenStream { + s.filter(|bi| { + !bi.ast() + .attrs + .iter() + .any(|attr| attr.path.is_ident("unsafe_ignore_trace")) + }); + let trace_body = s.each(|bi| quote!(mark(#bi))); + + s.add_bounds(AddBounds::Fields); + let trace_impl = s.unsafe_bound_impl( + quote!(::boa_gc::Trace), + quote! { + #[inline] + unsafe fn trace(&self) { + #[allow(dead_code)] + #[inline] + fn mark(it: &T) { + unsafe { + ::boa_gc::Trace::trace(it); + } + } + match *self { #trace_body } + } + #[inline] + unsafe fn weak_trace(&self) { + #[allow(dead_code, unreachable_code)] + #[inline] + fn mark(it: &T) { + unsafe { + ::boa_gc::Trace::weak_trace(it) + } + } + match *self { #trace_body } + } + #[inline] + unsafe fn root(&self) { + #[allow(dead_code)] + #[inline] + fn mark(it: &T) { + unsafe { + ::boa_gc::Trace::root(it); + } + } + match *self { #trace_body } + } + #[inline] + unsafe fn unroot(&self) { + #[allow(dead_code)] + #[inline] + fn mark(it: &T) { + unsafe { + ::boa_gc::Trace::unroot(it); + } + } + match *self { #trace_body } + } + #[inline] + fn run_finalizer(&self) { + ::boa_gc::Finalize::finalize(self); + #[allow(dead_code)] + #[inline] + fn mark(it: &T) { + unsafe { + ::boa_gc::Trace::run_finalizer(it); + } + } + match *self { #trace_body } + } + }, + ); + + // We also implement drop to prevent unsafe drop implementations on this + // type and encourage people to use Finalize. This implementation will + // call `Finalize::finalize` if it is safe to do so. + let drop_impl = s.unbound_impl( + quote!(::std::ops::Drop), + quote! { + fn drop(&mut self) { + if ::boa_gc::finalizer_safe() { + ::boa_gc::Finalize::finalize(self); + } + } + }, + ); + + quote! { + #trace_impl + #drop_impl + } +} + +decl_derive! { + [Finalize] => + /// Derive the Finalize trait. + derive_finalize +} + +#[allow(clippy::needless_pass_by_value)] +fn derive_finalize(s: Structure<'_>) -> proc_macro2::TokenStream { + s.unbound_impl(quote!(::boa_gc::Finalize), quote!()) +} diff --git a/boa_parser/src/error.rs b/boa_parser/src/error.rs index 324ffc03625..336bff5ca9d 100644 --- a/boa_parser/src/error.rs +++ b/boa_parser/src/error.rs @@ -1,7 +1,6 @@ //! Error and result implementation for the parser. use crate::lexer::Error as LexError; - use boa_ast::{Position, Span}; use std::fmt; @@ -26,34 +25,51 @@ impl From for Error { } } -/// An enum which represents errors encounted during parsing an expression +/// An enum which represents errors encountered during parsing an expression #[derive(Debug)] pub enum Error { /// When it expected a certain kind of token, but got another as part of something Expected { + /// The token(s) that were expected. expected: Box<[String]>, + + /// The token that was not expected. found: Box, - span: Span, + + /// The parsing context in which the error occurred. context: &'static str, + + /// Position of the source code where the error occurred. + span: Span, }, + /// When a token is unexpected Unexpected { + /// The error message. + message: Option<&'static str>, + + /// The token that was not expected. found: Box, + + /// Position of the source code where the error occurred. span: Span, - message: Option<&'static str>, }, + /// When there is an abrupt end to the parsing AbruptEnd, + /// A lexing error. - Lex { err: LexError }, + Lex { + /// The error that occurred during lexing. + err: LexError, + }, + /// Catch all General Error General { + /// The error message. message: &'static str, - position: Position, - }, - /// Unimplemented syntax error - Unimplemented { - message: &'static str, + + /// Position of the source code where the error occurred. position: Position, }, } @@ -73,6 +89,7 @@ impl Error { } /// Creates an `Expected` parsing error. + #[inline] pub(crate) fn expected(expected: E, found: F, span: Span, context: &'static str) -> Self where E: Into>, @@ -87,6 +104,7 @@ impl Error { } /// Creates an `Expected` parsing error. + #[inline] pub(crate) fn unexpected(found: F, span: Span, message: C) -> Self where F: Into>, @@ -100,12 +118,14 @@ impl Error { } /// Creates a "general" parsing error. - pub(crate) fn general(message: &'static str, position: Position) -> Self { + #[inline] + pub(crate) const fn general(message: &'static str, position: Position) -> Self { Self::General { message, position } } /// Creates a "general" parsing error with the specific error message for a wrong function declaration in non-strict mode. - pub(crate) fn wrong_function_declaration_non_strict(position: Position) -> Self { + #[inline] + pub(crate) const fn wrong_function_declaration_non_strict(position: Position) -> Self { Self::General { message: "In non-strict mode code, functions can only be declared at top level, inside a block, or as the body of an if statement.", position @@ -114,7 +134,7 @@ impl Error { /// Creates a "general" parsing error with the specific error message for a wrong function declaration with label. #[inline] - pub(crate) fn wrong_labelled_function_declaration(position: Position) -> Self { + pub(crate) const fn wrong_labelled_function_declaration(position: Position) -> Self { Self::General { message: "Labelled functions can only be declared at top level or inside a block", position, @@ -122,15 +142,10 @@ impl Error { } /// Creates a parsing error from a lexing error. - pub(crate) fn lex(e: LexError) -> Self { + #[inline] + pub(crate) const fn lex(e: LexError) -> Self { Self::Lex { err: e } } - - /// Creates a new `Unimplemented` parsing error. - #[allow(dead_code)] - pub(crate) fn unimplemented(message: &'static str, position: Position) -> Self { - Self::Unimplemented { message, position } - } } impl fmt::Display for Error { @@ -180,11 +195,7 @@ impl fmt::Display for Error { } => write!( f, "unexpected token '{found}'{} at line {}, col {}", - if let Some(m) = message { - format!(", {m}") - } else { - String::new() - }, + message.map_or_else(String::new, |m| format!(", {m}")), span.start().line_number(), span.start().column_number() ), @@ -196,12 +207,6 @@ impl fmt::Display for Error { position.column_number() ), Self::Lex { err } => fmt::Display::fmt(err, f), - Self::Unimplemented { message, position } => write!( - f, - "{message} not yet implemented at line {}, col {}", - position.line_number(), - position.column_number() - ), } } } diff --git a/boa_parser/src/lexer/comment.rs b/boa_parser/src/lexer/comment.rs index 127c6932ef3..b5db8a5b32b 100644 --- a/boa_parser/src/lexer/comment.rs +++ b/boa_parser/src/lexer/comment.rs @@ -1,7 +1,6 @@ //! This module implements lexing for comments used in the JavaScript programing language. -use super::{Cursor, Error, Tokenizer}; -use crate::lexer::{Token, TokenKind}; +use crate::lexer::{Cursor, Error, Token, TokenKind, Tokenizer}; use boa_ast::{Position, Span}; use boa_interner::Interner; use boa_profiler::Profiler; diff --git a/boa_parser/src/lexer/cursor.rs b/boa_parser/src/lexer/cursor.rs index 3359e621153..5dd890f8388 100644 --- a/boa_parser/src/lexer/cursor.rs +++ b/boa_parser/src/lexer/cursor.rs @@ -14,9 +14,10 @@ pub(super) struct Cursor { impl Cursor { /// Gets the current position of the cursor in the source code. #[inline] - pub(super) fn pos(&self) -> Position { + pub(super) const fn pos(&self) -> Position { self.pos } + /// Advances the position to the next column. #[inline] pub(super) fn next_column(&mut self) { @@ -34,7 +35,7 @@ impl Cursor { #[inline] /// Returns if strict mode is currently active. - pub(super) fn strict_mode(&self) -> bool { + pub(super) const fn strict_mode(&self) -> bool { self.strict_mode } @@ -120,11 +121,8 @@ where let _timer = Profiler::global().start_event("cursor::next_is_ascii_pred()", "Lexing"); Ok(match self.peek()? { - Some(byte) => match byte { - 0..=0x7F => pred(char::from(byte)), - _ => false, - }, - None => false, + Some(byte) if (0..=0x7F).contains(&byte) => pred(char::from(byte)), + Some(_) | None => false, }) } @@ -141,11 +139,7 @@ where { let _timer = Profiler::global().start_event("cursor::next_is_char_pred()", "Lexing"); - Ok(if let Some(peek) = self.peek_char()? { - pred(peek) - } else { - false - }) + Ok(self.peek_char()?.map_or(false, pred)) } /// Fills the buffer with all bytes until the stop byte is found. @@ -303,7 +297,7 @@ struct InnerIter { impl InnerIter { /// Creates a new inner iterator. #[inline] - fn new(iter: Bytes) -> Self { + const fn new(iter: Bytes) -> Self { Self { iter, num_peeked_bytes: 0, @@ -503,7 +497,7 @@ fn utf8_acc_cont_byte(ch: u32, byte: u8) -> u32 { /// Checks whether the byte is a UTF-8 first byte (i.e., ascii byte or starts with the /// bits `11`). #[inline] -fn utf8_is_first_byte(byte: u8) -> bool { +const fn utf8_is_first_byte(byte: u8) -> bool { byte <= 0x7F || (byte >> 6) == 0x11 } @@ -513,7 +507,7 @@ fn unwrap_or_0(opt: Option) -> u8 { } #[inline] -fn utf8_len(ch: u32) -> u32 { +const fn utf8_len(ch: u32) -> u32 { if ch <= 0x7F { 1 } else if ch <= 0x7FF { diff --git a/boa_parser/src/lexer/error.rs b/boa_parser/src/lexer/error.rs index d24044aa9a2..7221a43314f 100644 --- a/boa_parser/src/lexer/error.rs +++ b/boa_parser/src/lexer/error.rs @@ -8,6 +8,7 @@ use boa_ast::Position; use std::{error::Error as StdError, fmt, io}; +/// An error that occurred during the lexing. #[derive(Debug)] pub enum Error { /// An IO error is raised to indicate an issue when the lexer is reading data that isn't diff --git a/boa_parser/src/lexer/identifier.rs b/boa_parser/src/lexer/identifier.rs index 6a63cc78e35..d66038d3061 100644 --- a/boa_parser/src/lexer/identifier.rs +++ b/boa_parser/src/lexer/identifier.rs @@ -1,7 +1,6 @@ //! This module implements lexing for identifiers (foo, myvar, etc.) used in the JavaScript programing language. -use super::{Cursor, Error, Tokenizer}; -use crate::lexer::{StringLiteral, Token, TokenKind}; +use crate::lexer::{Cursor, Error, StringLiteral, Token, TokenKind, Tokenizer}; use boa_ast::{Keyword, Position, Span}; use boa_interner::Interner; use boa_profiler::Profiler; @@ -23,7 +22,8 @@ pub(super) struct Identifier { impl Identifier { /// Creates a new identifier/keyword lexer. - pub(super) fn new(init: char) -> Self { + #[inline] + pub(super) const fn new(init: char) -> Self { Self { init } } @@ -33,13 +33,10 @@ impl Identifier { /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-names-and-keywords + #[inline] pub(super) fn is_identifier_start(ch: u32) -> bool { matches!(ch, 0x0024 /* $ */ | 0x005F /* _ */) - || if let Ok(ch) = char::try_from(ch) { - ch.is_id_start() - } else { - false - } + || char::try_from(ch).map_or(false, char::is_id_start) } /// Checks if a character is `IdentifierPart` as per ECMAScript standards. @@ -48,15 +45,12 @@ impl Identifier { /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-names-and-keywords + #[inline] fn is_identifier_part(ch: u32) -> bool { matches!( ch, 0x0024 /* $ */ | 0x005F /* _ */ | 0x200C /* */ | 0x200D /* */ - ) || if let Ok(ch) = char::try_from(ch) { - ch.is_id_continue() - } else { - false - } + ) || char::try_from(ch).map_or(false, char::is_id_continue) } } @@ -75,15 +69,12 @@ impl Tokenizer for Identifier { let (identifier_name, contains_escaped_chars) = Self::take_identifier_name(cursor, start_pos, self.init)?; - let token_kind = if let Ok(keyword) = identifier_name.parse() { - match keyword { - Keyword::True => TokenKind::BooleanLiteral(true), - Keyword::False => TokenKind::BooleanLiteral(false), - Keyword::Null => TokenKind::NullLiteral, - _ => TokenKind::Keyword((keyword, contains_escaped_chars)), - } - } else { - TokenKind::identifier(interner.get_or_intern(identifier_name.as_str())) + let token_kind = match identifier_name.parse() { + Ok(Keyword::True) => TokenKind::BooleanLiteral(true), + Ok(Keyword::False) => TokenKind::BooleanLiteral(false), + Ok(Keyword::Null) => TokenKind::NullLiteral, + Ok(keyword) => TokenKind::Keyword((keyword, contains_escaped_chars)), + _ => TokenKind::identifier(interner.get_or_intern(identifier_name.as_str())), }; Ok(Token::new(token_kind, Span::new(start_pos, cursor.pos()))) diff --git a/boa_parser/src/lexer/mod.rs b/boa_parser/src/lexer/mod.rs index eb7aaa2908c..7c2c17adae0 100644 --- a/boa_parser/src/lexer/mod.rs +++ b/boa_parser/src/lexer/mod.rs @@ -14,18 +14,19 @@ //! //! [spec]: https://tc39.es/ecma262/#sec-ecmascript-language-lexical-grammar +pub mod error; +pub mod regex; +pub mod token; + mod comment; mod cursor; -pub mod error; mod identifier; mod number; mod operator; mod private_identifier; -pub mod regex; mod spread; mod string; mod template; -pub mod token; #[cfg(test)] mod tests; @@ -80,7 +81,7 @@ impl Lexer { /// * ECMAScript standard uses `\{Space_Separator}` + `\u{0009}`, `\u{000B}`, `\u{000C}`, `\u{FEFF}` /// /// [More information](https://tc39.es/ecma262/#table-32) - fn is_whitespace(ch: u32) -> bool { + const fn is_whitespace(ch: u32) -> bool { matches!( ch, 0x0020 | 0x0009 | 0x000B | 0x000C | 0x00A0 | 0xFEFF | @@ -97,13 +98,13 @@ impl Lexer { /// Gets the goal symbol the lexer is currently using. #[inline] - pub(crate) fn get_goal(&self) -> InputElement { + pub(crate) const fn get_goal(&self) -> InputElement { self.goal_symbol } #[inline] /// Returns if strict mode is currently active. - pub(super) fn strict_mode(&self) -> bool { + pub(super) const fn strict_mode(&self) -> bool { self.cursor.strict_mode() } diff --git a/boa_parser/src/lexer/number.rs b/boa_parser/src/lexer/number.rs index fddaf9cc1e8..82127aeb022 100644 --- a/boa_parser/src/lexer/number.rs +++ b/boa_parser/src/lexer/number.rs @@ -25,7 +25,7 @@ pub(super) struct NumberLiteral { impl NumberLiteral { /// Creates a new string literal lexer. - pub(super) fn new(init: u8) -> Self { + pub(super) const fn new(init: u8) -> Self { Self { init } } } @@ -42,7 +42,7 @@ enum NumericKind { impl NumericKind { /// Get the base of the number kind. - fn base(self) -> u32 { + const fn base(self) -> u32 { match self { Self::Rational => 10, Self::Integer(base) | Self::BigInt(base) => base, @@ -402,12 +402,10 @@ impl Tokenizer for NumberLiteral { } }, NumericKind::Integer(base) => { - if let Ok(num) = i32::from_str_radix(num_str, base) { - Numeric::Integer(num) - } else { + i32::from_str_radix(num_str, base).map_or_else(|_| { let num = BigInt::parse_bytes(num_str.as_bytes(), base).expect("Failed to parse integer after checks"); Numeric::Rational(num.to_f64().unwrap_or(f64::INFINITY)) - } + }, Numeric::Integer) } }; diff --git a/boa_parser/src/lexer/operator.rs b/boa_parser/src/lexer/operator.rs index b36d2dcc809..acfb686c4b5 100644 --- a/boa_parser/src/lexer/operator.rs +++ b/boa_parser/src/lexer/operator.rs @@ -37,16 +37,6 @@ macro_rules! vop { _ => $op, } }); - ($cursor:ident, $op:expr, {$($case:pat => $block:expr),+}) => { - match $cursor.peek().ok_or_else(|| Error::syntax("could not preview next value", $cursor.pos()))? { - $($case => { - $cursor.next_byte()?; - $cursor.next_column(); - $block - })+, - _ => $op - } - } } /// The `op` macro handles binary operations or assignment operations and converts them into tokens. @@ -83,7 +73,7 @@ pub(super) struct Operator { /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators impl Operator { /// Creates a new operator lexer. - pub(super) fn new(init: u8) -> Self { + pub(super) const fn new(init: u8) -> Self { Self { init } } } diff --git a/boa_parser/src/lexer/private_identifier.rs b/boa_parser/src/lexer/private_identifier.rs index f33a1a19fe0..0d1bd852489 100644 --- a/boa_parser/src/lexer/private_identifier.rs +++ b/boa_parser/src/lexer/private_identifier.rs @@ -1,7 +1,6 @@ //! This module implements lexing for private identifiers (#foo, #myvar, etc.) used in the JavaScript programing language. -use super::{identifier::Identifier, Cursor, Error, Tokenizer}; -use crate::lexer::{Token, TokenKind}; +use crate::lexer::{identifier::Identifier, Cursor, Error, Token, TokenKind, Tokenizer}; use boa_ast::{Position, Span}; use boa_interner::Interner; use boa_profiler::Profiler; @@ -18,7 +17,7 @@ pub(super) struct PrivateIdentifier; impl PrivateIdentifier { /// Creates a new private identifier lexer. - pub(super) fn new() -> Self { + pub(super) const fn new() -> Self { Self } } diff --git a/boa_parser/src/lexer/regex.rs b/boa_parser/src/lexer/regex.rs index 187a932bc00..6b2a9f39ccb 100644 --- a/boa_parser/src/lexer/regex.rs +++ b/boa_parser/src/lexer/regex.rs @@ -1,7 +1,6 @@ //! This module implements lexing for regex literals used in the JavaScript programing language. -use super::{Cursor, Error, Span, Tokenizer}; -use crate::lexer::{Token, TokenKind}; +use crate::lexer::{Cursor, Error, Span, Token, TokenKind, Tokenizer}; use bitflags::bitflags; use boa_ast::Position; use boa_interner::{Interner, Sym}; @@ -141,12 +140,27 @@ bitflags! { /// Flags of a regular expression. #[derive(Default)] pub struct RegExpFlags: u8 { + /// Whether to test the regular expression against all possible matches in a string, + /// or only against the first. const GLOBAL = 0b0000_0001; + + /// Whether to ignore case while attempting a match in a string. const IGNORE_CASE = 0b0000_0010; + + /// Whether or not to search in strings across multiple lines. const MULTILINE = 0b0000_0100; + + /// Whether `.` matches newlines or not. const DOT_ALL = 0b0000_1000; + + /// Whether or not Unicode features are enabled. const UNICODE = 0b0001_0000; + + /// Whether or not the search is sticky. const STICKY = 0b0010_0000; + + /// Whether the regular expression result exposes the start and end indices of + /// captured substrings. const HAS_INDICES = 0b0100_0000; } } diff --git a/boa_parser/src/lexer/spread.rs b/boa_parser/src/lexer/spread.rs index e6d7d9396b7..b26b8f5d47b 100644 --- a/boa_parser/src/lexer/spread.rs +++ b/boa_parser/src/lexer/spread.rs @@ -1,7 +1,6 @@ //! This module implements lexing for spread (...) literals used in the JavaScript programing language. -use super::{Cursor, Error, Tokenizer}; -use crate::lexer::Token; +use crate::lexer::{Cursor, Error, Token, Tokenizer}; use boa_ast::{Position, Punctuator, Span}; use boa_interner::Interner; use boa_profiler::Profiler; @@ -22,8 +21,8 @@ pub(super) struct SpreadLiteral; impl SpreadLiteral { /// Creates a new string literal lexer. - pub(super) fn new() -> Self { - Self {} + pub(super) const fn new() -> Self { + Self } } diff --git a/boa_parser/src/lexer/string.rs b/boa_parser/src/lexer/string.rs index 93005920196..38f44ef2d8c 100644 --- a/boa_parser/src/lexer/string.rs +++ b/boa_parser/src/lexer/string.rs @@ -1,7 +1,6 @@ //! This module implements lexing for string literals used in the JavaScript programing language. -use super::{Cursor, Error, Tokenizer}; -use crate::lexer::{Token, TokenKind}; +use crate::lexer::{Cursor, Error, Token, TokenKind, Tokenizer}; use boa_ast::{Position, Span}; use boa_interner::Interner; use boa_profiler::Profiler; @@ -109,7 +108,7 @@ impl StringLiteral { /// /// [spec]: https://tc39.es/ecma262/#prod-LineTerminator #[inline] - pub(super) fn is_line_terminator(ch: u32) -> bool { + pub(super) const fn is_line_terminator(ch: u32) -> bool { matches!( ch, 0x000A /* */ | 0x000D /* */ | 0x2028 /* */ | 0x2029 /* */ diff --git a/boa_parser/src/lexer/template.rs b/boa_parser/src/lexer/template.rs index 00d88675397..b1ab0e12cb5 100644 --- a/boa_parser/src/lexer/template.rs +++ b/boa_parser/src/lexer/template.rs @@ -1,9 +1,8 @@ //! This module implements lexing for template literals used in the JavaScript programing language. -use super::{Cursor, Error, Tokenizer}; use crate::lexer::{ string::{StringLiteral, UTF16CodeUnitsBuffer}, - Token, TokenKind, + Cursor, Error, Token, TokenKind, Tokenizer, }; use boa_ast::{Position, Span}; use boa_interner::{Interner, Sym}; @@ -22,7 +21,7 @@ pub struct TemplateString { impl TemplateString { /// Creates a new `TemplateString` with the given raw template ans start position. - pub fn new(raw: Sym, start_pos: Position) -> Self { + pub const fn new(raw: Sym, start_pos: Position) -> Self { Self { raw, start_pos } } @@ -32,7 +31,7 @@ impl TemplateString { /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-static-semantics-templatestrings - pub fn as_raw(self) -> Sym { + pub const fn as_raw(self) -> Sym { self.raw } diff --git a/boa_parser/src/lexer/tests.rs b/boa_parser/src/lexer/tests.rs index 5e810ae607f..01616e1f4da 100644 --- a/boa_parser/src/lexer/tests.rs +++ b/boa_parser/src/lexer/tests.rs @@ -5,11 +5,9 @@ use crate::lexer::{ template::TemplateString, token::Numeric, Cursor, Error, Interner, Lexer, Position, Punctuator, Read, Span, TokenKind, }; - use boa_ast::Keyword; use boa_interner::Sym; use boa_macros::utf16; - use std::str; fn span(start: (u32, u32), end: (u32, u32)) -> Span { @@ -610,11 +608,9 @@ fn single_number_without_semicolon() { let mut lexer = Lexer::new(&b"1"[..]); let interner = &mut Interner::default(); - if let Some(x) = lexer.next(interner).unwrap() { - assert_eq!(x.kind(), &TokenKind::numeric_literal(Numeric::Integer(1))); - } else { - panic!("Failed to lex 1 without semicolon"); - } + let expected = [TokenKind::numeric_literal(Numeric::Integer(1))]; + + expect_tokens(&mut lexer, &expected, interner); } #[test] @@ -864,11 +860,7 @@ fn take_while_char_pred_utf8_char() { let mut buf: Vec = Vec::new(); cur.take_while_char_pred(&mut buf, &|c| { - if let Ok(c) = char::try_from(c) { - c == 'a' || c == 'b' || c == 'c' || c == '😀' - } else { - false - } + char::try_from(c).map_or(false, |c| c == 'a' || c == 'b' || c == 'c' || c == '😀') }) .unwrap(); diff --git a/boa_parser/src/lexer/token.rs b/boa_parser/src/lexer/token.rs index db783fd1e9a..df752d6893f 100644 --- a/boa_parser/src/lexer/token.rs +++ b/boa_parser/src/lexer/token.rs @@ -29,21 +29,21 @@ impl Token { /// Create a new detailed token from the token data, line number and column number #[inline] #[must_use] - pub fn new(kind: TokenKind, span: Span) -> Self { + pub const fn new(kind: TokenKind, span: Span) -> Self { Self { kind, span } } /// Gets the kind of the token. #[inline] #[must_use] - pub fn kind(&self) -> &TokenKind { + pub const fn kind(&self) -> &TokenKind { &self.kind } /// Gets the token span in the original source code. #[inline] #[must_use] - pub fn span(&self) -> Span { + pub const fn span(&self) -> Span { self.span } @@ -63,7 +63,7 @@ pub enum Numeric { /// An integer Integer(i32), - // A BigInt + /// A BigInt BigInt(Box), } @@ -162,19 +162,19 @@ impl From for TokenKind { impl TokenKind { /// Creates a `BooleanLiteral` token kind. #[must_use] - pub fn boolean_literal(lit: bool) -> Self { + pub const fn boolean_literal(lit: bool) -> Self { Self::BooleanLiteral(lit) } /// Creates an `EOF` token kind. #[must_use] - pub fn eof() -> Self { + pub const fn eof() -> Self { Self::EOF } /// Creates an `Identifier` token type. #[must_use] - pub fn identifier(ident: Sym) -> Self { + pub const fn identifier(ident: Sym) -> Self { Self::Identifier(ident) } @@ -188,41 +188,43 @@ impl TokenKind { /// Creates a `Punctuator` token type. #[must_use] - pub fn punctuator(punc: Punctuator) -> Self { + pub const fn punctuator(punc: Punctuator) -> Self { Self::Punctuator(punc) } /// Creates a `StringLiteral` token type. #[must_use] - pub fn string_literal(lit: Sym) -> Self { + pub const fn string_literal(lit: Sym) -> Self { Self::StringLiteral(lit) } + /// Creates a `TemplateMiddle` token type. #[must_use] - pub fn template_middle(template_string: TemplateString) -> Self { + pub const fn template_middle(template_string: TemplateString) -> Self { Self::TemplateMiddle(template_string) } + /// Creates a `TemplateNoSubstitution` token type. #[must_use] - pub fn template_no_substitution(template_string: TemplateString) -> Self { + pub const fn template_no_substitution(template_string: TemplateString) -> Self { Self::TemplateNoSubstitution(template_string) } /// Creates a `RegularExpressionLiteral` token kind. #[must_use] - pub fn regular_expression_literal(body: Sym, flags: Sym) -> Self { + pub const fn regular_expression_literal(body: Sym, flags: Sym) -> Self { Self::RegularExpressionLiteral(body, flags) } /// Creates a `LineTerminator` token kind. #[must_use] - pub fn line_terminator() -> Self { + pub const fn line_terminator() -> Self { Self::LineTerminator } /// Creates a 'Comment' token kind. #[must_use] - pub fn comment() -> Self { + pub const fn comment() -> Self { Self::Comment } diff --git a/boa_parser/src/lib.rs b/boa_parser/src/lib.rs index 53ba00cc0fd..372efae3a15 100644 --- a/boa_parser/src/lib.rs +++ b/boa_parser/src/lib.rs @@ -9,46 +9,63 @@ //! [grammar]: https://tc39.es/ecma262/#sec-ecmascript-language-expressions #![cfg_attr(not(test), forbid(clippy::unwrap_used))] -#![warn( - clippy::perf, - clippy::single_match_else, - clippy::dbg_macro, - clippy::doc_markdown, - clippy::wildcard_imports, - clippy::struct_excessive_bools, - clippy::doc_markdown, - clippy::semicolon_if_nothing_returned, - clippy::pedantic -)] +#![warn(missing_docs, clippy::dbg_macro)] #![deny( - clippy::all, - clippy::cast_lossless, - clippy::redundant_closure_for_method_calls, - clippy::unnested_or_patterns, - clippy::trivially_copy_pass_by_ref, - clippy::needless_pass_by_value, - clippy::match_wildcard_for_single_variants, - clippy::map_unwrap_or, - unused_qualifications, + // rustc lint groups https://doc.rust-lang.org/rustc/lints/groups.html + warnings, + future_incompatible, + let_underscore, + nonstandard_style, + rust_2018_compatibility, + rust_2018_idioms, + rust_2021_compatibility, + unused, + + // rustc allowed-by-default lints https://doc.rust-lang.org/rustc/lints/listing/allowed-by-default.html + macro_use_extern_crate, + meta_variable_misuse, + missing_abi, + missing_copy_implementations, + missing_debug_implementations, + non_ascii_idents, + noop_method_call, + single_use_lifetimes, + trivial_casts, + trivial_numeric_casts, + unreachable_pub, + unsafe_op_in_unsafe_fn, + unused_crate_dependencies, unused_import_braces, unused_lifetimes, - unreachable_pub, - trivial_numeric_casts, + unused_qualifications, + unused_tuple_struct_fields, + variant_size_differences, + + // rustdoc lints https://doc.rust-lang.org/rustdoc/lints.html rustdoc::broken_intra_doc_links, - missing_debug_implementations, - missing_copy_implementations, - deprecated_in_future, - meta_variable_misuse, - non_ascii_idents, - rust_2018_compatibility, - rust_2018_idioms, - future_incompatible, - nonstandard_style + rustdoc::private_intra_doc_links, + rustdoc::missing_crate_level_docs, + rustdoc::private_doc_tests, + rustdoc::invalid_codeblock_attributes, + rustdoc::invalid_rust_codeblocks, + rustdoc::bare_urls, + + // clippy categories https://doc.rust-lang.org/clippy/ + clippy::all, + clippy::correctness, + clippy::suspicious, + clippy::style, + clippy::complexity, + clippy::perf, + clippy::pedantic, + clippy::nursery, )] #![allow( clippy::module_name_repetitions, clippy::too_many_lines, - clippy::let_unit_value + clippy::cognitive_complexity, + clippy::let_unit_value, + clippy::redundant_pub_crate )] pub mod error; diff --git a/boa_parser/src/parser/cursor/buffered_lexer/mod.rs b/boa_parser/src/parser/cursor/buffered_lexer/mod.rs index 44828cef695..f8c3edf098c 100644 --- a/boa_parser/src/parser/cursor/buffered_lexer/mod.rs +++ b/boa_parser/src/parser/cursor/buffered_lexer/mod.rs @@ -105,7 +105,7 @@ where } #[inline] - pub(super) fn strict_mode(&self) -> bool { + pub(super) const fn strict_mode(&self) -> bool { self.lexer.strict_mode() } diff --git a/boa_parser/src/parser/cursor/mod.rs b/boa_parser/src/parser/cursor/mod.rs index 8f260c84b84..39d97ac2fbf 100644 --- a/boa_parser/src/parser/cursor/mod.rs +++ b/boa_parser/src/parser/cursor/mod.rs @@ -87,10 +87,8 @@ where /// This function will panic if there is no further token in the cursor. #[inline] #[track_caller] - #[allow(clippy::let_underscore_drop)] pub(super) fn advance(&mut self, interner: &mut Interner) { - let _ = self - .next(interner) + self.next(interner) .expect("tried to advance cursor, but the buffer was empty"); } @@ -108,7 +106,7 @@ where /// Gets the current strict mode for the cursor. #[inline] - pub(super) fn strict_mode(&self) -> bool { + pub(super) const fn strict_mode(&self) -> bool { self.buffered_lexer.strict_mode() } @@ -120,7 +118,7 @@ where /// Returns if the cursor is currently in an arrow function declaration. #[inline] - pub(super) fn arrow(&self) -> bool { + pub(super) const fn arrow(&self) -> bool { self.arrow } @@ -132,7 +130,7 @@ where /// Returns if the cursor is currently used in `JSON.parse`. #[inline] - pub(super) fn json_parse(&self) -> bool { + pub(super) const fn json_parse(&self) -> bool { self.json_parse } @@ -156,15 +154,18 @@ where identifier: Sym, position: Position, ) -> ParseResult<()> { - if let Some(env) = self.private_environments_stack.last_mut() { - env.entry(identifier).or_insert(position); - Ok(()) - } else { - Err(Error::general( - "private identifier declared outside of class", - position, - )) - } + self.private_environments_stack.last_mut().map_or_else( + || { + Err(Error::general( + "private identifier declared outside of class", + position, + )) + }, + |env| { + env.entry(identifier).or_insert(position); + Ok(()) + }, + ) } /// Pop the last private environment. @@ -231,14 +232,14 @@ where &mut self, interner: &mut Interner, ) -> ParseResult> { - match self.buffered_lexer.peek(0, false, interner)? { - Some(tk) => match tk.kind() { + self.buffered_lexer.peek(0, false, interner)?.map_or( + Ok(SemicolonResult::Found(None)), + |tk| match tk.kind() { TokenKind::Punctuator(Punctuator::Semicolon | Punctuator::CloseBlock) | TokenKind::LineTerminator => Ok(SemicolonResult::Found(Some(tk))), _ => Ok(SemicolonResult::NotFound(tk)), }, - None => Ok(SemicolonResult::Found(None)), - } + ) } /// Consumes the next token if it is a semicolon, or returns a `Errpr` if it's not. @@ -306,11 +307,11 @@ where skip_n: usize, interner: &mut Interner, ) -> ParseResult> { - if let Some(t) = self.buffered_lexer.peek(skip_n, false, interner)? { - Ok(Some(t.kind() == &TokenKind::LineTerminator)) - } else { - Ok(None) - } + self.buffered_lexer + .peek(skip_n, false, interner)? + .map_or(Ok(None), |t| { + Ok(Some(t.kind() == &TokenKind::LineTerminator)) + }) } /// Advance the cursor to the next token and retrieve it, only if it's of `kind` type. diff --git a/boa_parser/src/parser/expression/assignment/mod.rs b/boa_parser/src/parser/expression/assignment/mod.rs index 5b163d63573..afe6ece672f 100644 --- a/boa_parser/src/parser/expression/assignment/mod.rs +++ b/boa_parser/src/parser/expression/assignment/mod.rs @@ -13,6 +13,7 @@ mod conditional; mod exponentiation; mod r#yield; +use super::check_strict_arguments_or_eval; use crate::{ lexer::{Error as LexError, InputElement, TokenKind}, parser::{ @@ -41,8 +42,6 @@ use std::io::Read; pub(super) use exponentiation::ExponentiationExpression; -use super::check_strict_arguments_or_eval; - /// Assignment expression parsing. /// /// This can be one of the following: diff --git a/boa_parser/src/parser/expression/left_hand_side/optional/mod.rs b/boa_parser/src/parser/expression/left_hand_side/optional/mod.rs index d89c3b5ff5b..1653a7c821c 100644 --- a/boa_parser/src/parser/expression/left_hand_side/optional/mod.rs +++ b/boa_parser/src/parser/expression/left_hand_side/optional/mod.rs @@ -1,26 +1,22 @@ #[cfg(test)] mod tests; -use std::io::Read; - -use boa_interner::{Interner, Sym}; -use boa_profiler::Profiler; - use crate::{ lexer::{Token, TokenKind}, parser::{ - cursor::Cursor, expression::Expression, AllowAwait, AllowYield, OrAbrupt, ParseResult, - TokenParser, + cursor::Cursor, expression::left_hand_side::arguments::Arguments, expression::Expression, + AllowAwait, AllowYield, OrAbrupt, ParseResult, TokenParser, }, Error, }; - -use super::arguments::Arguments; use boa_ast::{ self as ast, expression::{access::PropertyAccessField, Optional, OptionalOperation, OptionalOperationKind}, Punctuator, }; +use boa_interner::{Interner, Sym}; +use boa_profiler::Profiler; +use std::io::Read; /// Parses an optional expression. /// diff --git a/boa_parser/src/parser/expression/mod.rs b/boa_parser/src/parser/expression/mod.rs index 8740f4fbdfa..fa47b764218 100644 --- a/boa_parser/src/parser/expression/mod.rs +++ b/boa_parser/src/parser/expression/mod.rs @@ -736,7 +736,7 @@ expression!( ); /// Returns an error if `arguments` or `eval` are used as identifier in strict mode. -fn check_strict_arguments_or_eval(ident: Identifier, position: Position) -> ParseResult<()> { +const fn check_strict_arguments_or_eval(ident: Identifier, position: Position) -> ParseResult<()> { match ident.sym() { Sym::ARGUMENTS => Err(Error::general( "unexpected identifier 'arguments' in strict mode", diff --git a/boa_parser/src/parser/expression/primary/array_initializer/mod.rs b/boa_parser/src/parser/expression/primary/array_initializer/mod.rs index 547bfa9d148..658441467ea 100644 --- a/boa_parser/src/parser/expression/primary/array_initializer/mod.rs +++ b/boa_parser/src/parser/expression/primary/array_initializer/mod.rs @@ -124,10 +124,8 @@ where } } - if last_spread { - if let Some(None) = elements.last() { - has_trailing_comma_spread = true; - } + if last_spread && elements.last() == Some(&None) { + has_trailing_comma_spread = true; } Ok(literal::ArrayLiteral::new( diff --git a/boa_parser/src/parser/expression/primary/async_function_expression/mod.rs b/boa_parser/src/parser/expression/primary/async_function_expression/mod.rs index 2da894c56b0..12d8d7f40d2 100644 --- a/boa_parser/src/parser/expression/primary/async_function_expression/mod.rs +++ b/boa_parser/src/parser/expression/primary/async_function_expression/mod.rs @@ -77,10 +77,9 @@ where if cursor.strict_mode() && [Sym::EVAL, Sym::ARGUMENTS].contains(&name.sym()) { return Err(Error::lex(LexError::Syntax( "Unexpected eval or arguments in strict mode".into(), - match cursor.peek(0, interner)? { - Some(token) => token.span().end(), - None => Position::new(1, 1), - }, + cursor + .peek(0, interner)? + .map_or_else(|| Position::new(1, 1), |token| token.span().end()), ))); } } diff --git a/boa_parser/src/parser/expression/primary/async_generator_expression/mod.rs b/boa_parser/src/parser/expression/primary/async_generator_expression/mod.rs index 6e3ef65476c..660452da602 100644 --- a/boa_parser/src/parser/expression/primary/async_generator_expression/mod.rs +++ b/boa_parser/src/parser/expression/primary/async_generator_expression/mod.rs @@ -86,10 +86,9 @@ where if cursor.strict_mode() && [Sym::EVAL, Sym::ARGUMENTS].contains(&name.sym()) { return Err(Error::lex(LexError::Syntax( "Unexpected eval or arguments in strict mode".into(), - match cursor.peek(0, interner)? { - Some(token) => token.span().end(), - None => Position::new(1, 1), - }, + cursor + .peek(0, interner)? + .map_or_else(|| Position::new(1, 1), |token| token.span().end()), ))); } } diff --git a/boa_parser/src/parser/expression/primary/function_expression/mod.rs b/boa_parser/src/parser/expression/primary/function_expression/mod.rs index fda58036fec..4903e50c0bc 100644 --- a/boa_parser/src/parser/expression/primary/function_expression/mod.rs +++ b/boa_parser/src/parser/expression/primary/function_expression/mod.rs @@ -79,10 +79,9 @@ where if cursor.strict_mode() && [Sym::EVAL, Sym::ARGUMENTS].contains(&name.sym()) { return Err(Error::lex(LexError::Syntax( "Unexpected eval or arguments in strict mode".into(), - match cursor.peek(0, interner)? { - Some(token) => token.span().end(), - None => Position::new(1, 1), - }, + cursor + .peek(0, interner)? + .map_or_else(|| Position::new(1, 1), |token| token.span().end()), ))); } } diff --git a/boa_parser/src/parser/expression/primary/generator_expression/mod.rs b/boa_parser/src/parser/expression/primary/generator_expression/mod.rs index 5a12db96823..8f9c920afb8 100644 --- a/boa_parser/src/parser/expression/primary/generator_expression/mod.rs +++ b/boa_parser/src/parser/expression/primary/generator_expression/mod.rs @@ -82,10 +82,9 @@ where if cursor.strict_mode() && [Sym::EVAL, Sym::ARGUMENTS].contains(&name.sym()) { return Err(Error::lex(LexError::Syntax( "Unexpected eval or arguments in strict mode".into(), - match cursor.peek(0, interner)? { - Some(token) => token.span().end(), - None => Position::new(1, 1), - }, + cursor + .peek(0, interner)? + .map_or_else(|| Position::new(1, 1), |token| token.span().end()), ))); } } diff --git a/boa_parser/src/parser/expression/primary/mod.rs b/boa_parser/src/parser/expression/primary/mod.rs index 3a1cd8553ad..33a142dc75d 100644 --- a/boa_parser/src/parser/expression/primary/mod.rs +++ b/boa_parser/src/parser/expression/primary/mod.rs @@ -450,8 +450,8 @@ where } }; - let is_arrow = if let Some(TokenKind::Punctuator(Punctuator::Arrow)) = - cursor.peek(0, interner)?.map(Token::kind) + let is_arrow = if cursor.peek(0, interner)?.map(Token::kind) + == Some(&TokenKind::Punctuator(Punctuator::Arrow)) { !cursor.peek_is_line_terminator(0, interner).or_abrupt()? } else { diff --git a/boa_parser/src/parser/expression/primary/object_initializer/mod.rs b/boa_parser/src/parser/expression/primary/object_initializer/mod.rs index cf975a74c8a..006c91be999 100644 --- a/boa_parser/src/parser/expression/primary/object_initializer/mod.rs +++ b/boa_parser/src/parser/expression/primary/object_initializer/mod.rs @@ -121,10 +121,9 @@ where if let Some(position) = duplicate_proto_position { if !cursor.json_parse() - && match cursor.peek(0, interner)? { - Some(token) => token.kind() != &TokenKind::Punctuator(Punctuator::Assign), - None => true, - } + && cursor.peek(0, interner)?.map_or(true, |token| { + token.kind() != &TokenKind::Punctuator(Punctuator::Assign) + }) { return Err(Error::general( "Duplicate __proto__ fields are not allowed in object literals.", diff --git a/boa_parser/src/parser/expression/update.rs b/boa_parser/src/parser/expression/update.rs index 421c8400580..52fe12ba960 100644 --- a/boa_parser/src/parser/expression/update.rs +++ b/boa_parser/src/parser/expression/update.rs @@ -5,12 +5,14 @@ //! //! [spec]: https://tc39.es/ecma262/#sec-update-expressions -use super::{check_strict_arguments_or_eval, left_hand_side::LeftHandSideExpression}; use crate::{ lexer::{Error as LexError, TokenKind}, parser::{ - expression::unary::UnaryExpression, AllowAwait, AllowYield, Cursor, OrAbrupt, ParseResult, - TokenParser, + expression::{ + check_strict_arguments_or_eval, left_hand_side::LeftHandSideExpression, + unary::UnaryExpression, + }, + AllowAwait, AllowYield, Cursor, OrAbrupt, ParseResult, TokenParser, }, Error, }; diff --git a/boa_parser/src/parser/function/mod.rs b/boa_parser/src/parser/function/mod.rs index 7803d452e7b..fd58d9bf475 100644 --- a/boa_parser/src/parser/function/mod.rs +++ b/boa_parser/src/parser/function/mod.rs @@ -15,7 +15,7 @@ use crate::{ parser::{ expression::{BindingIdentifier, Initializer}, statement::{ArrayBindingPattern, ObjectBindingPattern, StatementList}, - AllowAwait, AllowYield, Cursor, TokenParser, + AllowAwait, AllowYield, Cursor, OrAbrupt, ParseResult, TokenParser, }, Error, }; @@ -30,8 +30,6 @@ use boa_macros::utf16; use boa_profiler::Profiler; use std::io::Read; -use super::{OrAbrupt, ParseResult}; - /// Formal parameters parsing. /// /// More information: diff --git a/boa_parser/src/parser/mod.rs b/boa_parser/src/parser/mod.rs index 9a884380a79..e3f835db61a 100644 --- a/boa_parser/src/parser/mod.rs +++ b/boa_parser/src/parser/mod.rs @@ -9,9 +9,6 @@ pub(crate) mod function; #[cfg(test)] mod tests; -use rustc_hash::FxHashSet; -use std::io::Read; - use crate::{ error::ParseResult, lexer::TokenKind, @@ -31,6 +28,8 @@ use boa_ast::{ }; use boa_interner::Interner; use boa_macros::utf16; +use rustc_hash::FxHashSet; +use std::io::Read; /// Trait implemented by parsers. /// @@ -240,7 +239,7 @@ pub struct Script { impl Script { /// Create a new `Script` parser. #[inline] - fn new(direct_eval: bool) -> Self { + const fn new(direct_eval: bool) -> Self { Self { direct_eval } } } @@ -315,7 +314,7 @@ pub struct ScriptBody { impl ScriptBody { /// Create a new `ScriptBody` parser. #[inline] - fn new(direct_eval: bool) -> Self { + const fn new(direct_eval: bool) -> Self { Self { direct_eval } } } diff --git a/boa_parser/src/parser/statement/block/mod.rs b/boa_parser/src/parser/statement/block/mod.rs index d13b1554458..f0cf432ae4a 100644 --- a/boa_parser/src/parser/statement/block/mod.rs +++ b/boa_parser/src/parser/statement/block/mod.rs @@ -10,10 +10,12 @@ #[cfg(test)] mod tests; -use super::StatementList; use crate::{ lexer::TokenKind, - parser::{AllowAwait, AllowReturn, AllowYield, Cursor, OrAbrupt, ParseResult, TokenParser}, + parser::{ + statement::StatementList, AllowAwait, AllowReturn, AllowYield, Cursor, OrAbrupt, + ParseResult, TokenParser, + }, Error, }; use boa_ast::{ diff --git a/boa_parser/src/parser/statement/break_stm/mod.rs b/boa_parser/src/parser/statement/break_stm/mod.rs index 3271b01c1ad..8efb2f982ca 100644 --- a/boa_parser/src/parser/statement/break_stm/mod.rs +++ b/boa_parser/src/parser/statement/break_stm/mod.rs @@ -11,7 +11,7 @@ mod tests; use crate::{ - lexer::TokenKind, + lexer::{Token, TokenKind}, parser::{ cursor::{Cursor, SemicolonResult}, expression::LabelIdentifier, @@ -62,11 +62,8 @@ where cursor.expect((Keyword::Break, false), "break statement", interner)?; let label = if let SemicolonResult::Found(tok) = cursor.peek_semicolon(interner)? { - match tok { - Some(tok) if tok.kind() == &TokenKind::Punctuator(Punctuator::Semicolon) => { - cursor.advance(interner); - } - _ => {} + if tok.map(Token::kind) == Some(&TokenKind::Punctuator(Punctuator::Semicolon)) { + cursor.advance(interner); } None diff --git a/boa_parser/src/parser/statement/declaration/hoistable/class_decl/mod.rs b/boa_parser/src/parser/statement/declaration/hoistable/class_decl/mod.rs index f71f65f811b..412703cae9c 100644 --- a/boa_parser/src/parser/statement/declaration/hoistable/class_decl/mod.rs +++ b/boa_parser/src/parser/statement/declaration/hoistable/class_decl/mod.rs @@ -660,7 +660,7 @@ where match class_element_name { ClassElementName::PropertyName(property_name) if r#static => { - if let Some(Sym::PROTOTYPE) = property_name.prop_name() { + if property_name.prop_name() == Some(Sym::PROTOTYPE) { return Err(Error::general( "class may not have static method definitions named 'prototype'", name_position, @@ -717,7 +717,7 @@ where cursor.set_strict_mode(strict); match class_element_name { ClassElementName::PropertyName(property_name) if r#static => { - if let Some(Sym::PROTOTYPE) = property_name.prop_name() { + if property_name.prop_name() == Some(Sym::PROTOTYPE) { return Err(Error::general( "class may not have static method definitions named 'prototype'", name_position, @@ -762,7 +762,7 @@ where match class_element_name { ClassElementName::PropertyName(property_name) if r#static => { - if let Some(Sym::PROTOTYPE) = property_name.prop_name() { + if property_name.prop_name() == Some(Sym::PROTOTYPE) { return Err(Error::general( "class may not have static method definitions named 'prototype'", name_position, diff --git a/boa_parser/src/parser/statement/declaration/hoistable/mod.rs b/boa_parser/src/parser/statement/declaration/hoistable/mod.rs index 55f406cc6d1..5a82e10571e 100644 --- a/boa_parser/src/parser/statement/declaration/hoistable/mod.rs +++ b/boa_parser/src/parser/statement/declaration/hoistable/mod.rs @@ -165,10 +165,9 @@ fn parse_callable_declaration( if cursor.strict_mode() && [Sym::EVAL, Sym::ARGUMENTS].contains(&name.sym()) { return Err(Error::lex(LexError::Syntax( "Unexpected eval or arguments in strict mode".into(), - match cursor.peek(0, interner)? { - Some(token) => token.span().end(), - None => Position::new(1, 1), - }, + cursor + .peek(0, interner)? + .map_or_else(|| Position::new(1, 1), |token| token.span().end()), ))); } diff --git a/boa_parser/src/parser/statement/if_stm/mod.rs b/boa_parser/src/parser/statement/if_stm/mod.rs index 53603b38251..5c660953a00 100644 --- a/boa_parser/src/parser/statement/if_stm/mod.rs +++ b/boa_parser/src/parser/statement/if_stm/mod.rs @@ -4,8 +4,9 @@ mod tests; use crate::{ lexer::TokenKind, parser::{ - expression::Expression, statement::declaration::FunctionDeclaration, AllowAwait, - AllowReturn, AllowYield, Cursor, OrAbrupt, ParseResult, TokenParser, + expression::Expression, + statement::{declaration::FunctionDeclaration, Statement}, + AllowAwait, AllowReturn, AllowYield, Cursor, OrAbrupt, ParseResult, TokenParser, }, Error, }; @@ -17,8 +18,6 @@ use boa_interner::Interner; use boa_profiler::Profiler; use std::io::Read; -use super::Statement; - /// If statement parsing. /// /// An `if` statement will have a condition, a block statement, and an optional `else` statement. diff --git a/boa_parser/src/parser/statement/iteration/do_while_statement.rs b/boa_parser/src/parser/statement/iteration/do_while_statement.rs index 6f4d1cde72b..57f252cf5e1 100644 --- a/boa_parser/src/parser/statement/iteration/do_while_statement.rs +++ b/boa_parser/src/parser/statement/iteration/do_while_statement.rs @@ -8,7 +8,7 @@ //! [spec]: https://tc39.es/ecma262/#sec-do-while-statement use crate::{ - lexer::TokenKind, + lexer::{Token, TokenKind}, parser::{ expression::Expression, statement::Statement, AllowAwait, AllowReturn, AllowYield, Cursor, OrAbrupt, ParseResult, TokenParser, @@ -107,10 +107,10 @@ where // Here, we only care to read the next token if it's a semicolon. If it's not, we // automatically "enter" or assume a semicolon, since we have just read the `)` token: // https://tc39.es/ecma262/#sec-automatic-semicolon-insertion - if let Some(tok) = cursor.peek(0, interner)? { - if let TokenKind::Punctuator(Punctuator::Semicolon) = *tok.kind() { - cursor.advance(interner); - } + if cursor.peek(0, interner)?.map(Token::kind) + == Some(&TokenKind::Punctuator(Punctuator::Semicolon)) + { + cursor.advance(interner); } Ok(DoWhileLoop::new(body, cond)) diff --git a/boa_parser/src/parser/statement/labelled_stm/mod.rs b/boa_parser/src/parser/statement/labelled_stm/mod.rs index cda3d665f36..0b93edb487b 100644 --- a/boa_parser/src/parser/statement/labelled_stm/mod.rs +++ b/boa_parser/src/parser/statement/labelled_stm/mod.rs @@ -3,7 +3,7 @@ use crate::{ parser::{ cursor::Cursor, expression::LabelIdentifier, - statement::{AllowAwait, AllowReturn, Statement}, + statement::{declaration::FunctionDeclaration, AllowAwait, AllowReturn, Statement}, AllowYield, OrAbrupt, ParseResult, TokenParser, }, Error, @@ -13,8 +13,6 @@ use boa_interner::Interner; use boa_profiler::Profiler; use std::io::Read; -use super::declaration::FunctionDeclaration; - /// Labelled Statement Parsing /// /// More information diff --git a/boa_parser/src/parser/statement/return_stm/mod.rs b/boa_parser/src/parser/statement/return_stm/mod.rs index 56947b633de..6d8afc070a5 100644 --- a/boa_parser/src/parser/statement/return_stm/mod.rs +++ b/boa_parser/src/parser/statement/return_stm/mod.rs @@ -1,5 +1,5 @@ use crate::{ - lexer::TokenKind, + lexer::{Token, TokenKind}, parser::{ cursor::{Cursor, SemicolonResult}, expression::Expression, @@ -50,11 +50,8 @@ where cursor.expect((Keyword::Return, false), "return statement", interner)?; if let SemicolonResult::Found(tok) = cursor.peek_semicolon(interner)? { - match tok { - Some(tok) if tok.kind() == &TokenKind::Punctuator(Punctuator::Semicolon) => { - cursor.advance(interner); - } - _ => {} + if tok.map(Token::kind) == Some(&TokenKind::Punctuator(Punctuator::Semicolon)) { + cursor.advance(interner); } return Ok(Return::new(None)); diff --git a/boa_profiler/src/lib.rs b/boa_profiler/src/lib.rs index c1a543aa763..c57cce497b2 100644 --- a/boa_profiler/src/lib.rs +++ b/boa_profiler/src/lib.rs @@ -1,16 +1,71 @@ -#![allow(missing_copy_implementations, missing_debug_implementations)] +//! Profiler for the Boa JavaScript engine. + +#![cfg_attr(not(test), forbid(clippy::unwrap_used))] +#![warn(missing_docs, clippy::dbg_macro)] +#![deny( + // rustc lint groups https://doc.rust-lang.org/rustc/lints/groups.html + warnings, + future_incompatible, + let_underscore, + nonstandard_style, + rust_2018_compatibility, + rust_2018_idioms, + rust_2021_compatibility, + unused, + + // rustc allowed-by-default lints https://doc.rust-lang.org/rustc/lints/listing/allowed-by-default.html + macro_use_extern_crate, + meta_variable_misuse, + missing_abi, + missing_copy_implementations, + missing_debug_implementations, + non_ascii_idents, + noop_method_call, + single_use_lifetimes, + trivial_casts, + trivial_numeric_casts, + unreachable_pub, + unsafe_op_in_unsafe_fn, + unused_crate_dependencies, + unused_import_braces, + unused_lifetimes, + unused_qualifications, + unused_tuple_struct_fields, + variant_size_differences, + + // rustdoc lints https://doc.rust-lang.org/rustdoc/lints.html + rustdoc::broken_intra_doc_links, + rustdoc::private_intra_doc_links, + rustdoc::missing_crate_level_docs, + rustdoc::private_doc_tests, + rustdoc::invalid_codeblock_attributes, + rustdoc::invalid_rust_codeblocks, + rustdoc::bare_urls, + + // clippy categories https://doc.rust-lang.org/clippy/ + clippy::all, + clippy::correctness, + clippy::suspicious, + clippy::style, + clippy::complexity, + clippy::perf, + clippy::pedantic, + clippy::nursery, +)] + +use std::fmt::{self, Debug}; #[cfg(feature = "profiler")] use measureme::{EventId, Profiler as MeasuremeProfiler, TimingGuard}; #[cfg(feature = "profiler")] use once_cell::sync::OnceCell; -use std::fmt::{self, Debug}; #[cfg(feature = "profiler")] use std::{ path::Path, thread::{current, ThreadId}, }; +/// Profiler for the Boa JavaScript engine. #[cfg(feature = "profiler")] pub struct Profiler { profiler: MeasuremeProfiler, @@ -24,6 +79,7 @@ static mut INSTANCE: OnceCell = OnceCell::new(); #[cfg(feature = "profiler")] impl Profiler { + /// Start a new profiled event. pub fn start_event(&self, label: &str, category: &str) -> TimingGuard<'_> { let kind = self.profiler.alloc_string(category); let id = EventId::from_label(self.profiler.alloc_string(label)); @@ -32,16 +88,19 @@ impl Profiler { .start_recording_interval_event(kind, id, thread_id) } - pub fn default() -> Self { + fn default() -> Self { let profiler = MeasuremeProfiler::new(Path::new("./my_trace")).expect("must be able to create file"); Self { profiler } } + /// Return the global instance of the profiler. + #[must_use] pub fn global() -> &'static Self { unsafe { INSTANCE.get_or_init(Self::default) } } + /// Drop the global instance of the profiler. pub fn drop(&self) { // In order to drop the INSTANCE we need to get ownership of it, which isn't possible on a static unless you make it a mutable static // mutating statics is unsafe, so we need to wrap it as so. @@ -58,6 +117,7 @@ impl Profiler { // Once `as_64()` is in stable we can do this: // https://github.com/rust-lang/rust/pull/68531/commits/ea42b1c5b85f649728e3a3b334489bac6dce890a // Until then our options are: use rust-nightly or use unsafe {} + #[allow(clippy::cast_possible_truncation)] fn thread_id_to_u32(tid: ThreadId) -> u32 { unsafe { std::mem::transmute::(tid) as u32 } } @@ -69,17 +129,24 @@ impl Debug for Profiler { } } +/// An empty profiler that does nothing. #[cfg(not(feature = "profiler"))] +#[derive(Copy, Clone)] pub struct Profiler; -#[allow(clippy::unused_unit, clippy::unused_self)] +//#[allow(clippy::unused_unit, clippy::unused_self)] #[cfg(not(feature = "profiler"))] impl Profiler { - pub fn start_event(&self, _label: &str, _category: &str) -> () {} + /// Does nothing. + #[allow(clippy::unused_unit)] + pub const fn start_event(&self, _label: &str, _category: &str) -> () {} - pub fn drop(&self) {} + /// Does nothing. + pub const fn drop(&self) {} - pub fn global() -> Self { + /// Does nothing. + #[must_use] + pub const fn global() -> Self { Self } } diff --git a/boa_tester/Cargo.toml b/boa_tester/Cargo.toml index d3f9bc968f3..10f8cf5159e 100644 --- a/boa_tester/Cargo.toml +++ b/boa_tester/Cargo.toml @@ -13,18 +13,17 @@ rust-version.workspace = true [dependencies] boa_engine = { workspace = true, features = ["intl"] } -boa_interner.workspace = true boa_gc.workspace = true boa_parser.workspace = true -clap = { version = "4.0.22", features = ["derive"] } +clap = { version = "4.0.26", features = ["derive"] } serde = { version = "1.0.147", features = ["derive"] } serde_yaml = "0.9.14" -serde_json = "1.0.87" +serde_json = "1.0.89" bitflags = "1.3.2" regex = "1.7.0" once_cell = "1.16.0" colored = "2.0.0" fxhash = "0.2.1" -gc = { version = "0.4.1", features = ["derive"] } -rayon = "1.5.3" -anyhow = "1.0.66" +rayon = "1.6.0" +toml = "0.5.9" +color-eyre = "0.6.2" diff --git a/boa_tester/src/exec/js262.rs b/boa_tester/src/exec/js262.rs index 8cdb1118cbb..89d5c4602e8 100644 --- a/boa_tester/src/exec/js262.rs +++ b/boa_tester/src/exec/js262.rs @@ -78,8 +78,9 @@ fn detach_array_buffer(_this: &JsValue, args: &[JsValue], _: &mut Context) -> Js /// /// Accepts a string value as its first argument and executes it as an ECMAScript script. fn eval_script(_this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { - if let Some(source_text) = args.get(0).and_then(JsValue::as_string) { - match context.parse(source_text.to_std_string_escaped()) { + args.get(0).and_then(JsValue::as_string).map_or_else( + || Ok(JsValue::undefined()), + |source_text| match context.parse(source_text.to_std_string_escaped()) { // TODO: check strict Err(e) => Err(JsNativeError::typ() .with_message(format!("Uncaught Syntax Error: {e}")) @@ -87,10 +88,8 @@ fn eval_script(_this: &JsValue, args: &[JsValue], context: &mut Context) -> JsRe // Calling eval here parses the code a second time. // TODO: We can fix this after we have have defined the public api for the vm executer. Ok(_) => context.eval(source_text.to_std_string_escaped()), - } - } else { - Ok(JsValue::undefined()) - } + }, + ) } /// The `$262.gc()` function. diff --git a/boa_tester/src/exec/mod.rs b/boa_tester/src/exec/mod.rs index 4310c8219e0..89eba98563c 100644 --- a/boa_tester/src/exec/mod.rs +++ b/boa_tester/src/exec/mod.rs @@ -2,20 +2,19 @@ mod js262; -use crate::read::ErrorType; - use super::{ - Harness, Outcome, Phase, SuiteResult, Test, TestFlags, TestOutcomeResult, TestResult, - TestSuite, IGNORED, + Harness, Outcome, Phase, SuiteResult, Test, TestFlags, TestOutcomeResult, TestResult, TestSuite, }; +use crate::read::ErrorType; use boa_engine::{ builtins::JsArgs, object::FunctionBuilder, property::Attribute, Context, JsNativeErrorKind, JsResult, JsValue, }; -use boa_gc::{Cell, Finalize, Gc, Trace}; +use boa_gc::{Finalize, Gc, GcCell, Trace}; use boa_parser::Parser; use colored::Colorize; use rayon::prelude::*; +use std::borrow::Cow; impl TestSuite { /// Runs the test suite. @@ -130,6 +129,24 @@ impl Test { /// Runs the test once, in strict or non-strict mode fn run_once(&self, harness: &Harness, strict: bool, verbose: u8) -> TestResult { + if self.ignored { + if verbose > 1 { + println!( + "`{}`{}: {}", + self.name, + if strict { " (strict mode)" } else { "" }, + "Ignored".yellow() + ); + } else { + print!("{}", "-".yellow()); + } + return TestResult { + name: self.name.clone(), + strict, + result: TestOutcomeResult::Ignored, + result_text: Box::default(), + }; + } if verbose > 1 { println!( "`{}`{}: starting", @@ -139,183 +156,143 @@ impl Test { } let test_content = if strict { - format!("\"use strict\";\n{}", self.content) + Cow::Owned(format!("\"use strict\";\n{}", self.content)) } else { - self.content.to_string() + Cow::Borrowed(&*self.content) }; - let (result, result_text) = if !IGNORED.contains_any_flag(self.flags) - && !IGNORED.contains_test(&self.name) - && !IGNORED.contains_any_feature(&self.features) - && (matches!(self.expected_outcome, Outcome::Positive) - || matches!( - self.expected_outcome, - Outcome::Negative { - phase: Phase::Parse, - error_type: _, - } - ) - || matches!( - self.expected_outcome, - Outcome::Negative { - phase: Phase::Early, - error_type: _, - } - ) - || matches!( - self.expected_outcome, - Outcome::Negative { - phase: Phase::Runtime, - error_type: _, - } - )) { - let res = std::panic::catch_unwind(|| match self.expected_outcome { - Outcome::Positive => { - let mut context = Context::default(); - let async_result = AsyncResult::default(); - - if let Err(e) = self.set_up_env(harness, &mut context, async_result.clone()) { - return (false, e); - } + let result = std::panic::catch_unwind(|| match self.expected_outcome { + Outcome::Positive => { + let mut context = Context::default(); + let async_result = AsyncResult::default(); - // TODO: timeout - let value = match context.eval(&test_content) { - Ok(v) => v, - Err(e) => return (false, format!("Uncaught {e}")), - }; + if let Err(e) = self.set_up_env(harness, &mut context, async_result.clone()) { + return (false, e); + } - if let Err(e) = async_result.inner.borrow().as_ref() { - return (false, format!("Uncaught {e}")); - } + // TODO: timeout + let value = match context.eval(&*test_content) { + Ok(v) => v, + Err(e) => return (false, format!("Uncaught {e}")), + }; - (true, value.display().to_string()) + if let Err(e) = async_result.inner.borrow().as_ref() { + return (false, format!("Uncaught {e}")); } - Outcome::Negative { - phase: Phase::Parse | Phase::Early, + + (true, value.display().to_string()) + } + Outcome::Negative { + phase: Phase::Parse | Phase::Early, + error_type, + } => { + assert_eq!( error_type, - } => { - assert_eq!( - error_type, - ErrorType::SyntaxError, - "non-SyntaxError parsing/early error found in {}", - self.name - ); - - let mut context = Context::default(); - match context.parse(&test_content) { - Ok(statement_list) => match context.compile(&statement_list) { - Ok(_) => (false, "StatementList compilation should fail".to_owned()), - Err(e) => (true, format!("Uncaught {e:?}")), - }, - Err(e) => (true, format!("Uncaught {e}")), - } + ErrorType::SyntaxError, + "non-SyntaxError parsing/early error found in {}", + self.name + ); + + let mut context = Context::default(); + match context.parse(&*test_content) { + Ok(statement_list) => match context.compile(&statement_list) { + Ok(_) => (false, "StatementList compilation should fail".to_owned()), + Err(e) => (true, format!("Uncaught {e:?}")), + }, + Err(e) => (true, format!("Uncaught {e}")), } - Outcome::Negative { - phase: Phase::Resolution, - error_type: _, - } => todo!("check module resolution errors"), - Outcome::Negative { - phase: Phase::Runtime, - error_type, - } => { - let mut context = Context::default(); - if let Err(e) = self.set_up_env(harness, &mut context, AsyncResult::default()) { - return (false, e); - } - let code = match Parser::new(test_content.as_bytes()) - .parse_all(context.interner_mut()) - .map_err(Into::into) - .and_then(|stmts| context.compile(&stmts)) - { - Ok(code) => code, - Err(e) => return (false, format!("Uncaught {e}")), - }; - - // TODO: timeout - let e = match context.execute(code) { - Ok(res) => return (false, res.display().to_string()), - Err(e) => e, - }; - if let Ok(e) = e.try_native(&mut context) { - match &e.kind { - JsNativeErrorKind::Syntax if error_type == ErrorType::SyntaxError => {} - JsNativeErrorKind::Reference - if error_type == ErrorType::ReferenceError => {} - JsNativeErrorKind::Range if error_type == ErrorType::RangeError => {} - JsNativeErrorKind::Type if error_type == ErrorType::TypeError => {} - _ => return (false, format!("Uncaught {e}")), + } + Outcome::Negative { + phase: Phase::Resolution, + error_type: _, + } => todo!("check module resolution errors"), + Outcome::Negative { + phase: Phase::Runtime, + error_type, + } => { + let mut context = Context::default(); + if let Err(e) = self.set_up_env(harness, &mut context, AsyncResult::default()) { + return (false, e); + } + let code = match Parser::new(test_content.as_bytes()) + .parse_all(context.interner_mut()) + .map_err(Into::into) + .and_then(|stmts| context.compile(&stmts)) + { + Ok(code) => code, + Err(e) => return (false, format!("Uncaught {e}")), + }; + + // TODO: timeout + let e = match context.execute(code) { + Ok(res) => return (false, res.display().to_string()), + Err(e) => e, + }; + if let Ok(e) = e.try_native(&mut context) { + match &e.kind { + JsNativeErrorKind::Syntax if error_type == ErrorType::SyntaxError => {} + JsNativeErrorKind::Reference if error_type == ErrorType::ReferenceError => { } - (true, format!("Uncaught {e}")) - } else { - let passed = e - .as_opaque() - .expect("try_native cannot fail if e is not opaque") - .as_object() - .and_then(|o| o.get("constructor", &mut context).ok()) - .as_ref() - .and_then(JsValue::as_object) - .and_then(|o| o.get("name", &mut context).ok()) - .as_ref() - .and_then(JsValue::as_string) - .map(|s| s == error_type.as_str()) - .unwrap_or_default(); - (passed, format!("Uncaught {e}")) + JsNativeErrorKind::Range if error_type == ErrorType::RangeError => {} + JsNativeErrorKind::Type if error_type == ErrorType::TypeError => {} + _ => return (false, format!("Uncaught {e}")), } + (true, format!("Uncaught {e}")) + } else { + let passed = e + .as_opaque() + .expect("try_native cannot fail if e is not opaque") + .as_object() + .and_then(|o| o.get("constructor", &mut context).ok()) + .as_ref() + .and_then(JsValue::as_object) + .and_then(|o| o.get("name", &mut context).ok()) + .as_ref() + .and_then(JsValue::as_string) + .map(|s| s == error_type.as_str()) + .unwrap_or_default(); + (passed, format!("Uncaught {e}")) } - }); - - let result = res.map_or_else( - |_| { - eprintln!("last panic was on test \"{}\"", self.name); - (TestOutcomeResult::Panic, String::new()) - }, - |(res, text)| { - if res { - (TestOutcomeResult::Passed, text) - } else { - (TestOutcomeResult::Failed, text) - } - }, - ); - - if verbose > 1 { - println!( - "`{}`{}: {}", - self.name, - if strict { " (strict mode)" } else { "" }, - if matches!(result, (TestOutcomeResult::Passed, _)) { - "Passed".green() - } else if matches!(result, (TestOutcomeResult::Failed, _)) { - "Failed".red() - } else { - "⚠ Panic ⚠".red() - } - ); - } else { - print!( - "{}", - if matches!(result, (TestOutcomeResult::Passed, _)) { - ".".green() - } else { - "F".red() - } - ); } + }); + + let (result, result_text) = result.map_or_else( + |_| { + eprintln!("last panic was on test \"{}\"", self.name); + (TestOutcomeResult::Panic, String::new()) + }, + |(res, text)| { + if res { + (TestOutcomeResult::Passed, text) + } else { + (TestOutcomeResult::Failed, text) + } + }, + ); - result + if verbose > 1 { + println!( + "`{}`{}: {}", + self.name, + if strict { " (strict mode)" } else { "" }, + if result == TestOutcomeResult::Passed { + "Passed".green() + } else if result == TestOutcomeResult::Failed { + "Failed".red() + } else { + "⚠ Panic ⚠".red() + } + ); } else { - if verbose > 1 { - println!( - "`{}`{}: {}", - self.name, - if strict { " (strict mode)" } else { "" }, - "Ignored".yellow() - ); - } else { - print!("{}", "-".yellow()); - } - (TestOutcomeResult::Ignored, String::new()) - }; + print!( + "{}", + if result == TestOutcomeResult::Passed { + ".".green() + } else { + "F".red() + } + ); + } if verbose > 2 { println!( @@ -400,13 +377,13 @@ impl Test { /// Object which includes the result of the async operation. #[derive(Debug, Clone, Trace, Finalize)] struct AsyncResult { - inner: Gc>>, + inner: Gc>>, } impl Default for AsyncResult { fn default() -> Self { Self { - inner: Gc::new(Cell::new(Ok(()))), + inner: Gc::new(GcCell::new(Ok(()))), } } } diff --git a/boa_tester/src/main.rs b/boa_tester/src/main.rs index 54ef04d9a42..af02e8b2994 100644 --- a/boa_tester/src/main.rs +++ b/boa_tester/src/main.rs @@ -1,65 +1,70 @@ //! Test262 test runner //! //! This crate will run the full ECMAScript test suite (Test262) and report compliance of the -//! `boa` context. +//! `boa` engine. + #![doc( html_logo_url = "https://raw.githubusercontent.com/boa-dev/boa/main/assets/logo.svg", html_favicon_url = "https://raw.githubusercontent.com/boa-dev/boa/main/assets/logo.svg" )] #![cfg_attr(not(test), deny(clippy::unwrap_used))] -#![warn( - clippy::perf, - clippy::single_match_else, - clippy::dbg_macro, - clippy::doc_markdown, - clippy::wildcard_imports, - clippy::struct_excessive_bools, - clippy::doc_markdown, - clippy::semicolon_if_nothing_returned, - clippy::pedantic -)] +#![warn(missing_docs, clippy::dbg_macro)] #![deny( - clippy::all, - clippy::cast_lossless, - clippy::redundant_closure_for_method_calls, - clippy::unnested_or_patterns, - clippy::trivially_copy_pass_by_ref, - clippy::needless_pass_by_value, - clippy::match_wildcard_for_single_variants, - clippy::map_unwrap_or, - unused_qualifications, - unused_import_braces, - unused_lifetimes, - unreachable_pub, - trivial_numeric_casts, - // rustdoc, - missing_debug_implementations, - missing_copy_implementations, - deprecated_in_future, - meta_variable_misuse, - non_ascii_idents, - rust_2018_compatibility, - rust_2018_idioms, + // rustc lint groups https://doc.rust-lang.org/rustc/lints/groups.html + warnings, future_incompatible, + let_underscore, nonstandard_style, + rust_2018_compatibility, + rust_2018_idioms, + rust_2021_compatibility, + unused, + + // rustc allowed-by-default lints https://doc.rust-lang.org/rustc/lints/listing/allowed-by-default.html + macro_use_extern_crate, + meta_variable_misuse, + missing_abi, + missing_copy_implementations, + missing_debug_implementations, + non_ascii_idents, + noop_method_call, + single_use_lifetimes, + trivial_casts, + trivial_numeric_casts, + unreachable_pub, + unsafe_op_in_unsafe_fn, + unused_crate_dependencies, + unused_import_braces, + unused_lifetimes, + unused_qualifications, + unused_tuple_struct_fields, + variant_size_differences, + + // rustdoc lints https://doc.rust-lang.org/rustdoc/lints.html + rustdoc::broken_intra_doc_links, + rustdoc::private_intra_doc_links, + rustdoc::missing_crate_level_docs, + rustdoc::private_doc_tests, + rustdoc::invalid_codeblock_attributes, + rustdoc::invalid_rust_codeblocks, + rustdoc::bare_urls, + + // clippy categories https://doc.rust-lang.org/clippy/ + clippy::all, + clippy::correctness, + clippy::suspicious, + clippy::style, + clippy::complexity, + clippy::perf, + clippy::pedantic, + clippy::nursery, )] #![allow( - clippy::use_self, // TODO: deny once false positives are fixed - clippy::module_name_repetitions, - clippy::cast_possible_truncation, - clippy::cast_sign_loss, - clippy::cast_precision_loss, - clippy::cast_possible_wrap, - clippy::cast_ptr_alignment, - clippy::missing_panics_doc, + clippy::use_self, clippy::too_many_lines, - clippy::unreadable_literal, - clippy::missing_inline_in_public_items, - clippy::cognitive_complexity, - clippy::must_use_candidate, - clippy::missing_errors_doc, - clippy::as_conversions, - clippy::let_unit_value, + clippy::redundant_pub_crate, + clippy::cast_precision_loss, + clippy::cast_possible_wrap )] mod exec; @@ -70,26 +75,34 @@ use self::{ read::{read_harness, read_suite, read_test, MetaData, Negative, TestFlag}, results::{compare_results, write_json}, }; -use anyhow::{bail, Context}; use bitflags::bitflags; use clap::{ArgAction, Parser, ValueHint}; +use color_eyre::{ + eyre::{bail, WrapErr}, + Result, +}; use colored::Colorize; use fxhash::{FxHashMap, FxHashSet}; -use once_cell::sync::Lazy; use read::ErrorType; -use serde::{Deserialize, Serialize}; +use serde::{ + de::{Unexpected, Visitor}, + Deserialize, Deserializer, Serialize, +}; use std::{ - fs, + fs::{self, File}, + io::Read, path::{Path, PathBuf}, }; /// Structure to allow defining ignored tests, features and files that should /// be ignored even when reading. -#[derive(Debug)] +#[derive(Debug, Deserialize)] struct Ignored { + #[serde(default)] tests: FxHashSet>, + #[serde(default)] features: FxHashSet>, - files: FxHashSet>, + #[serde(default = "TestFlags::empty")] flags: TestFlags, } @@ -102,19 +115,20 @@ impl Ignored { /// Checks if the ignore list contains the given feature name in the list /// of features to ignore. - pub(crate) fn contains_any_feature(&self, features: &[Box]) -> bool { - features - .iter() - .any(|feature| self.features.contains(feature)) - } - - /// Checks if the ignore list contains the given file name in the list to - /// ignore from reading. - pub(crate) fn contains_file(&self, file: &str) -> bool { - self.files.contains(file) + pub(crate) fn contains_feature(&self, feature: &str) -> bool { + if self.features.contains(feature) { + return true; + } + // Some features are an accessor instead of a simple feature name e.g. `Intl.DurationFormat`. + // This ensures those are also ignored. + feature + .split('.') + .next() + .map(|feat| self.features.contains(feat)) + .unwrap_or_default() } - pub(crate) fn contains_any_flag(&self, flags: TestFlags) -> bool { + pub(crate) const fn contains_any_flag(&self, flags: TestFlags) -> bool { flags.intersects(self.flags) } } @@ -124,74 +138,11 @@ impl Default for Ignored { Self { tests: FxHashSet::default(), features: FxHashSet::default(), - files: FxHashSet::default(), flags: TestFlags::empty(), } } } -/// List of ignored tests. -static IGNORED: Lazy = Lazy::new(|| { - let path = Path::new("test_ignore.txt"); - if path.exists() { - let filtered = fs::read_to_string(path).expect("could not read test filters"); - filtered - .lines() - .filter(|line| !line.is_empty() && !line.starts_with("//")) - .fold(Ignored::default(), |mut ign, line| { - // let mut line = line.to_owned(); - if line.starts_with("file:") { - let file = line - .strip_prefix("file:") - .expect("prefix disappeared") - .trim() - .to_owned() - .into_boxed_str(); - let test = if file.ends_with(".js") { - file.strip_suffix(".js") - .expect("suffix disappeared") - .to_owned() - .into_boxed_str() - } else { - file.clone() - }; - ign.files.insert(file); - ign.tests.insert(test); - } else if line.starts_with("feature:") { - ign.features.insert( - line.strip_prefix("feature:") - .expect("prefix disappeared") - .trim() - .to_owned() - .into_boxed_str(), - ); - } else if line.starts_with("flag:") { - let flag = line - .strip_prefix("flag:") - .expect("prefix disappeared") - .trim() - .parse::() - .expect("invalid flag found"); - ign.flags.insert(flag.into()); - } else { - let mut test = line.trim(); - if test - .rsplit('.') - .next() - .map(|ext| ext.eq_ignore_ascii_case("js")) - == Some(true) - { - test = test.strip_suffix(".js").expect("suffix disappeared"); - } - ign.tests.insert(test.to_owned().into_boxed_str()); - } - ign - }) - } else { - Ignored::default() - } -}); - /// Boa test262 tester #[derive(Debug, Parser)] #[command(author, version, about, name = "Boa test262 tester")] @@ -217,6 +168,10 @@ enum Cli { /// Execute tests serially #[arg(short, long)] disable_parallelism: bool, + + /// Path to a TOML file with the ignored tests, features, flags and/or files. + #[arg(short, long, default_value = "test_ignore.toml", value_hint = ValueHint::FilePath)] + ignored: PathBuf, }, /// Compare two test suite results. Compare { @@ -235,7 +190,8 @@ enum Cli { } /// Program entry point. -fn main() { +fn main() -> Result<()> { + color_eyre::install()?; match Cli::parse() { Cli::Run { verbose, @@ -243,23 +199,15 @@ fn main() { suite, output, disable_parallelism, - } => { - if let Err(e) = run_test_suite( - verbose, - !disable_parallelism, - test262_path.as_path(), - suite.as_path(), - output.as_deref(), - ) { - eprintln!("Error: {e}"); - let mut src = e.source(); - while let Some(e) = src { - eprintln!(" caused by: {e}"); - src = e.source(); - } - std::process::exit(1); - } - } + ignored: ignore, + } => run_test_suite( + verbose, + !disable_parallelism, + test262_path.as_path(), + suite.as_path(), + output.as_deref(), + ignore.as_path(), + ), Cli::Compare { base, new, @@ -275,24 +223,33 @@ fn run_test_suite( test262_path: &Path, suite: &Path, output: Option<&Path>, -) -> anyhow::Result<()> { + ignored: &Path, +) -> Result<()> { if let Some(path) = output { if path.exists() { if !path.is_dir() { bail!("the output path must be a directory."); } } else { - fs::create_dir_all(path).context("could not create the output directory")?; + fs::create_dir_all(path).wrap_err("could not create the output directory")?; } } + let ignored = { + let mut input = String::new(); + let mut f = File::open(ignored).wrap_err("could not open ignored tests file")?; + f.read_to_string(&mut input) + .wrap_err("could not read ignored tests file")?; + toml::from_str(&input).wrap_err("could not decode ignored tests file")? + }; + if verbose != 0 { println!("Loading the test suite..."); } - let harness = read_harness(test262_path).context("could not read harness")?; + let harness = read_harness(test262_path).wrap_err("could not read harness")?; if suite.to_string_lossy().ends_with(".js") { - let test = read_test(&test262_path.join(suite)).with_context(|| { + let test = read_test(&test262_path.join(suite)).wrap_err_with(|| { let suite = suite.display(); format!("could not read the test {suite}") })?; @@ -304,7 +261,7 @@ fn run_test_suite( println!(); } else { - let suite = read_suite(&test262_path.join(suite)).with_context(|| { + let suite = read_suite(&test262_path.join(suite), &ignored, false).wrap_err_with(|| { let suite = suite.display(); format!("could not read the suite {suite}") })?; @@ -332,7 +289,7 @@ fn run_test_suite( ); write_json(results, output, verbose) - .context("could not write the results to the output JSON file")?; + .wrap_err("could not write the results to the output JSON file")?; } Ok(()) @@ -419,6 +376,7 @@ struct Test { includes: Box<[Box]>, locale: Locale, content: Box, + ignored: bool, } impl Test { @@ -440,15 +398,12 @@ impl Test { includes: metadata.includes, locale: metadata.locale, content: content.into(), + ignored: false, } } - /// Sets the name of the test. - fn set_name(&mut self, name: N) - where - N: Into>, - { - self.name = name.into(); + fn set_ignored(&mut self) { + self.ignored = true; } } @@ -477,15 +432,15 @@ impl From> for Outcome { bitflags! { struct TestFlags: u16 { - const STRICT = 0b000000001; - const NO_STRICT = 0b000000010; - const MODULE = 0b000000100; - const RAW = 0b000001000; - const ASYNC = 0b000010000; - const GENERATED = 0b000100000; - const CAN_BLOCK_IS_FALSE = 0b001000000; - const CAN_BLOCK_IS_TRUE = 0b010000000; - const NON_DETERMINISTIC = 0b100000000; + const STRICT = 0b0_0000_0001; + const NO_STRICT = 0b0_0000_0010; + const MODULE = 0b0_0000_0100; + const RAW = 0b0_0000_1000; + const ASYNC = 0b0_0001_0000; + const GENERATED = 0b0_0010_0000; + const CAN_BLOCK_IS_FALSE = 0b0_0100_0000; + const CAN_BLOCK_IS_TRUE = 0b0_1000_0000; + const NON_DETERMINISTIC = 0b1_0000_0000; } } @@ -534,6 +489,59 @@ where } } +impl<'de> Deserialize<'de> for TestFlags { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct FlagsVisitor; + + impl<'de> Visitor<'de> for FlagsVisitor { + type Value = TestFlags; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "a sequence of flags") + } + + fn visit_seq(self, mut seq: A) -> Result + where + A: serde::de::SeqAccess<'de>, + { + let mut flags = TestFlags::empty(); + while let Some(elem) = seq.next_element::()? { + flags |= elem.into(); + } + Ok(flags) + } + } + + struct RawFlagsVisitor; + + impl Visitor<'_> for RawFlagsVisitor { + type Value = TestFlags; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "a flags number") + } + + fn visit_u16(self, v: u16) -> Result + where + E: serde::de::Error, + { + TestFlags::from_bits(v).ok_or_else(|| { + E::invalid_value(Unexpected::Unsigned(v.into()), &"a valid flag number") + }) + } + } + + if deserializer.is_human_readable() { + deserializer.deserialize_seq(FlagsVisitor) + } else { + deserializer.deserialize_u16(RawFlagsVisitor) + } + } +} + /// Phase for an error. #[derive(Debug, Clone, Copy, Deserialize)] #[serde(rename_all = "lowercase")] diff --git a/boa_tester/src/read.rs b/boa_tester/src/read.rs index e198b64220a..0d187c416eb 100644 --- a/boa_tester/src/read.rs +++ b/boa_tester/src/read.rs @@ -1,10 +1,15 @@ //! Module to read the list of test suites from disk. -use super::{Harness, Locale, Phase, Test, TestSuite, IGNORED}; -use anyhow::Context; +use crate::Ignored; + +use super::{Harness, Locale, Phase, Test, TestSuite}; +use color_eyre::{ + eyre::{eyre, WrapErr}, + Result, +}; use fxhash::FxHashMap; use serde::Deserialize; -use std::{fs, io, path::Path, str::FromStr}; +use std::{fs, io, path::Path}; /// Representation of the YAML metadata in Test262 tests. #[derive(Debug, Clone, Deserialize)] @@ -48,13 +53,13 @@ pub(super) enum ErrorType { } impl ErrorType { - pub(super) fn as_str(self) -> &'static str { + pub(super) const fn as_str(self) -> &'static str { match self { - ErrorType::Test262Error => "Test262Error", - ErrorType::SyntaxError => "SyntaxError", - ErrorType::ReferenceError => "ReferenceError", - ErrorType::RangeError => "RangeError", - ErrorType::TypeError => "TypeError", + Self::Test262Error => "Test262Error", + Self::SyntaxError => "SyntaxError", + Self::ReferenceError => "ReferenceError", + Self::RangeError => "RangeError", + Self::TypeError => "TypeError", } } } @@ -77,31 +82,12 @@ pub(super) enum TestFlag { NonDeterministic, } -impl FromStr for TestFlag { - type Err = String; // TODO: improve error type. - - fn from_str(s: &str) -> Result { - match s { - "onlyStrict" => Ok(Self::OnlyStrict), - "noStrict" => Ok(Self::NoStrict), - "module" => Ok(Self::Module), - "raw" => Ok(Self::Raw), - "async" => Ok(Self::Async), - "generated" => Ok(Self::Generated), - "CanBlockIsFalse" => Ok(Self::CanBlockIsFalse), - "CanBlockIsTrue" => Ok(Self::CanBlockIsTrue), - "non-deterministic" => Ok(Self::NonDeterministic), - _ => Err(format!("unknown test flag: {s}")), - } - } -} - /// Reads the Test262 defined bindings. -pub(super) fn read_harness(test262_path: &Path) -> anyhow::Result { +pub(super) fn read_harness(test262_path: &Path) -> Result { let mut includes = FxHashMap::default(); - for entry in - fs::read_dir(test262_path.join("harness")).context("error reading the harness directory")? + for entry in fs::read_dir(test262_path.join("harness")) + .wrap_err("error reading the harness directory")? { let entry = entry?; let file_name = entry.file_name(); @@ -112,7 +98,7 @@ pub(super) fn read_harness(test262_path: &Path) -> anyhow::Result { } let content = fs::read_to_string(entry.path()) - .with_context(|| format!("error reading the harnes/{file_name}"))?; + .wrap_err_with(|| format!("error reading the harnes/{file_name}"))?; includes.insert( file_name.into_owned().into_boxed_str(), @@ -120,13 +106,13 @@ pub(super) fn read_harness(test262_path: &Path) -> anyhow::Result { ); } let assert = fs::read_to_string(test262_path.join("harness/assert.js")) - .context("error reading harnes/assert.js")? + .wrap_err("error reading harnes/assert.js")? .into_boxed_str(); let sta = fs::read_to_string(test262_path.join("harness/sta.js")) - .context("error reading harnes/sta.js")? + .wrap_err("error reading harnes/sta.js")? .into_boxed_str(); let doneprint_handle = fs::read_to_string(test262_path.join("harness/doneprintHandle.js")) - .context("error reading harnes/doneprintHandle.js")? + .wrap_err("error reading harnes/doneprintHandle.js")? .into_boxed_str(); Ok(Harness { @@ -138,38 +124,54 @@ pub(super) fn read_harness(test262_path: &Path) -> anyhow::Result { } /// Reads a test suite in the given path. -pub(super) fn read_suite(path: &Path) -> anyhow::Result { +pub(super) fn read_suite( + path: &Path, + ignored: &Ignored, + mut ignore_suite: bool, +) -> Result { let name = path .file_name() - .with_context(|| format!("test suite with no name found: {}", path.display()))? + .ok_or_else(|| eyre!(format!("test suite with no name found: {}", path.display())))? .to_str() - .with_context(|| format!("non-UTF-8 suite name found: {}", path.display()))?; + .ok_or_else(|| eyre!(format!("non-UTF-8 suite name found: {}", path.display())))?; + + ignore_suite |= ignored.contains_test(name); let mut suites = Vec::new(); let mut tests = Vec::new(); // TODO: iterate in parallel - for entry in path.read_dir().context("retrieving entry")? { + for entry in path.read_dir().wrap_err("retrieving entry")? { let entry = entry?; - if entry.file_type().context("retrieving file type")?.is_dir() { - suites.push(read_suite(entry.path().as_path()).with_context(|| { - let path = entry.path(); - let suite = path.display(); - format!("error reading sub-suite {suite}") - })?); + if entry.file_type().wrap_err("retrieving file type")?.is_dir() { + suites.push( + read_suite(entry.path().as_path(), ignored, ignore_suite).wrap_err_with(|| { + let path = entry.path(); + let suite = path.display(); + format!("error reading sub-suite {suite}") + })?, + ); } else if entry.file_name().to_string_lossy().contains("_FIXTURE") { continue; - } else if IGNORED.contains_file(&entry.file_name().to_string_lossy()) { - let mut test = Test::default(); - test.set_name(entry.file_name().to_string_lossy()); - tests.push(test); } else { - tests.push(read_test(entry.path().as_path()).with_context(|| { + let mut test = read_test(entry.path().as_path()).wrap_err_with(|| { let path = entry.path(); let suite = path.display(); format!("error reading test {suite}") - })?); + })?; + + if ignore_suite + || ignored.contains_any_flag(test.flags) + || ignored.contains_test(&test.name) + || test + .features + .iter() + .any(|feat| ignored.contains_feature(feat)) + { + test.set_ignored(); + } + tests.push(test); } } diff --git a/boa_tester/src/results.rs b/boa_tester/src/results.rs index c8063d28d11..f1edfa7ebc1 100644 --- a/boa_tester/src/results.rs +++ b/boa_tester/src/results.rs @@ -1,4 +1,5 @@ use super::SuiteResult; +use color_eyre::{eyre::WrapErr, Result}; use serde::{Deserialize, Serialize}; use std::{ env, fs, @@ -208,16 +209,16 @@ fn update_gh_pages_repo(path: &Path, verbose: u8) { } /// Compares the results of two test suite runs. -pub(crate) fn compare_results(base: &Path, new: &Path, markdown: bool) { +pub(crate) fn compare_results(base: &Path, new: &Path, markdown: bool) -> Result<()> { let base_results: ResultInfo = serde_json::from_reader(BufReader::new( - fs::File::open(base).expect("could not open the base results file"), + fs::File::open(base).wrap_err("could not open the base results file")?, )) - .expect("could not read the base results"); + .wrap_err("could not read the base results")?; let new_results: ResultInfo = serde_json::from_reader(BufReader::new( - fs::File::open(new).expect("could not open the new results file"), + fs::File::open(new).wrap_err("could not open the new results file")?, )) - .expect("could not read the new results"); + .wrap_err("could not read the new results")?; let base_total = base_results.results.total as isize; let new_total = new_results.results.total as isize; @@ -433,6 +434,8 @@ pub(crate) fn compare_results(base: &Path, new: &Path, markdown: bool) { } } } + + Ok(()) } /// Test differences. diff --git a/boa_unicode/src/lib.rs b/boa_unicode/src/lib.rs index d896e788e04..fd4ce3a45c6 100644 --- a/boa_unicode/src/lib.rs +++ b/boa_unicode/src/lib.rs @@ -11,60 +11,58 @@ html_favicon_url = "https://raw.githubusercontent.com/boa-dev/boa/main/assets/logo.svg" )] #![cfg_attr(not(test), forbid(clippy::unwrap_used))] -#![warn( - clippy::perf, - clippy::single_match_else, - clippy::dbg_macro, - clippy::doc_markdown, - clippy::wildcard_imports, - clippy::struct_excessive_bools, - clippy::doc_markdown, - clippy::semicolon_if_nothing_returned, - clippy::pedantic -)] +#![warn(missing_docs, clippy::dbg_macro)] #![deny( - clippy::all, - clippy::cast_lossless, - clippy::redundant_closure_for_method_calls, - clippy::use_self, - clippy::unnested_or_patterns, - clippy::trivially_copy_pass_by_ref, - clippy::needless_pass_by_value, - clippy::match_wildcard_for_single_variants, - clippy::map_unwrap_or, - unused_qualifications, - unused_import_braces, - unused_lifetimes, - unreachable_pub, - trivial_numeric_casts, - // rustdoc, - missing_debug_implementations, - missing_copy_implementations, - deprecated_in_future, - meta_variable_misuse, - non_ascii_idents, - rust_2018_compatibility, - rust_2018_idioms, + // rustc lint groups https://doc.rust-lang.org/rustc/lints/groups.html + warnings, future_incompatible, + let_underscore, nonstandard_style, + rust_2018_compatibility, + rust_2018_idioms, + rust_2021_compatibility, + unused, + + // rustc allowed-by-default lints https://doc.rust-lang.org/rustc/lints/listing/allowed-by-default.html + macro_use_extern_crate, + meta_variable_misuse, + missing_abi, + missing_copy_implementations, + missing_debug_implementations, + non_ascii_idents, + noop_method_call, + single_use_lifetimes, + trivial_casts, + trivial_numeric_casts, + unreachable_pub, + unsafe_op_in_unsafe_fn, + unused_crate_dependencies, + unused_import_braces, + unused_lifetimes, + unused_qualifications, + unused_tuple_struct_fields, + variant_size_differences, + + // rustdoc lints https://doc.rust-lang.org/rustdoc/lints.html + rustdoc::broken_intra_doc_links, + rustdoc::private_intra_doc_links, + rustdoc::missing_crate_level_docs, + rustdoc::private_doc_tests, + rustdoc::invalid_codeblock_attributes, + rustdoc::invalid_rust_codeblocks, + rustdoc::bare_urls, + + // clippy categories https://doc.rust-lang.org/clippy/ + clippy::all, + clippy::correctness, + clippy::suspicious, + clippy::style, + clippy::complexity, + clippy::perf, + clippy::pedantic, + clippy::nursery, )] -#![allow( - clippy::module_name_repetitions, - clippy::cast_possible_truncation, - clippy::cast_sign_loss, - clippy::cast_precision_loss, - clippy::cast_possible_wrap, - clippy::cast_ptr_alignment, - clippy::missing_panics_doc, - clippy::too_many_lines, - clippy::unreadable_literal, - clippy::missing_inline_in_public_items, - clippy::cognitive_complexity, - clippy::must_use_candidate, - clippy::missing_errors_doc, - clippy::as_conversions, - clippy::let_unit_value -)] +#![allow(clippy::redundant_pub_crate)] mod tables; #[cfg(test)] diff --git a/boa_wasm/src/lib.rs b/boa_wasm/src/lib.rs index 803ad9ecea0..85fb45be99e 100644 --- a/boa_wasm/src/lib.rs +++ b/boa_wasm/src/lib.rs @@ -1,66 +1,67 @@ +//! A ECMAScript WASM implementation based on boa_engine. + #![doc( html_logo_url = "https://raw.githubusercontent.com/boa-dev/boa/main/assets/logo.svg", html_favicon_url = "https://raw.githubusercontent.com/boa-dev/boa/main/assets/logo.svg" )] #![cfg_attr(not(test), forbid(clippy::unwrap_used))] -#![warn( - clippy::perf, - clippy::single_match_else, - clippy::dbg_macro, - clippy::doc_markdown, - clippy::wildcard_imports, - clippy::struct_excessive_bools, - clippy::doc_markdown, - clippy::semicolon_if_nothing_returned, - clippy::pedantic -)] +#![warn(missing_docs, clippy::dbg_macro)] #![deny( - clippy::all, - clippy::cast_lossless, - clippy::redundant_closure_for_method_calls, - clippy::use_self, - clippy::unnested_or_patterns, - clippy::trivially_copy_pass_by_ref, - clippy::needless_pass_by_value, - clippy::match_wildcard_for_single_variants, - clippy::map_unwrap_or, - unused_qualifications, - unused_import_braces, - unused_lifetimes, - unreachable_pub, - trivial_numeric_casts, - // rustdoc, - missing_debug_implementations, - missing_copy_implementations, - deprecated_in_future, - meta_variable_misuse, - non_ascii_idents, - rust_2018_compatibility, - rust_2018_idioms, + // rustc lint groups https://doc.rust-lang.org/rustc/lints/groups.html + warnings, future_incompatible, + let_underscore, nonstandard_style, -)] -#![allow( - clippy::module_name_repetitions, - clippy::cast_possible_truncation, - clippy::cast_sign_loss, - clippy::cast_precision_loss, - clippy::cast_possible_wrap, - clippy::cast_ptr_alignment, - clippy::missing_panics_doc, - clippy::too_many_lines, - clippy::unreadable_literal, - clippy::missing_inline_in_public_items, - clippy::cognitive_complexity, - clippy::must_use_candidate, - clippy::missing_errors_doc, - clippy::as_conversions, - clippy::let_unit_value + rust_2018_compatibility, + rust_2018_idioms, + rust_2021_compatibility, + unused, + + // rustc allowed-by-default lints https://doc.rust-lang.org/rustc/lints/listing/allowed-by-default.html + macro_use_extern_crate, + meta_variable_misuse, + missing_abi, + missing_copy_implementations, + missing_debug_implementations, + non_ascii_idents, + noop_method_call, + single_use_lifetimes, + trivial_casts, + trivial_numeric_casts, + unreachable_pub, + unsafe_op_in_unsafe_fn, + unused_crate_dependencies, + unused_import_braces, + unused_lifetimes, + unused_qualifications, + unused_tuple_struct_fields, + variant_size_differences, + + // rustdoc lints https://doc.rust-lang.org/rustdoc/lints.html + rustdoc::broken_intra_doc_links, + rustdoc::private_intra_doc_links, + rustdoc::missing_crate_level_docs, + rustdoc::private_doc_tests, + rustdoc::invalid_codeblock_attributes, + rustdoc::invalid_rust_codeblocks, + rustdoc::bare_urls, + + // clippy categories https://doc.rust-lang.org/clippy/ + clippy::all, + clippy::correctness, + clippy::suspicious, + clippy::style, + clippy::complexity, + clippy::perf, + clippy::pedantic, + clippy::nursery, )] use boa_engine::Context; +use getrandom as _; use wasm_bindgen::prelude::*; +/// Evaluate the given ECMAScript code. #[wasm_bindgen] pub fn evaluate(src: &str) -> Result { // Setup executor diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock index beb643d96dc..ab15b48ab56 100644 --- a/fuzz/Cargo.lock +++ b/fuzz/Cargo.lock @@ -59,7 +59,6 @@ dependencies = [ "chrono", "dyn-clone", "fast-float", - "gc", "indexmap", "num-bigint", "num-integer", @@ -93,7 +92,8 @@ dependencies = [ name = "boa_gc" version = "0.16.0" dependencies = [ - "gc", + "boa_macros", + "boa_profiler", ] [[package]] @@ -113,8 +113,10 @@ dependencies = [ name = "boa_macros" version = "0.16.0" dependencies = [ + "proc-macro2", "quote", "syn", + "synstructure", ] [[package]] @@ -167,9 +169,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.22" +version = "0.4.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfd4d1b31faaa3a89d7934dbded3111da0d2ef28e3ebccdb4f0179f5929d1ef1" +checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f" dependencies = [ "iana-time-zone", "js-sys", @@ -263,27 +265,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95765f67b4b18863968b4a1bd5bb576f732b29a4a28c7cd84c09fa3e2875f33c" -[[package]] -name = "gc" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3edaac0f5832202ebc99520cb77c932248010c4645d20be1dc62d6579f5b3752" -dependencies = [ - "gc_derive", -] - -[[package]] -name = "gc_derive" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60df8444f094ff7885631d80e78eb7d88c3c2361a98daaabb06256e4500db941" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "synstructure", -] - [[package]] name = "getrandom" version = "0.2.8" diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index cc86d751da9..bdd84c79cf4 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -27,3 +27,15 @@ 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 diff --git a/fuzz/README.md b/fuzz/README.md index 097e46ccb97..9bb314bfd6b 100644 --- a/fuzz/README.md +++ b/fuzz/README.md @@ -35,3 +35,22 @@ following: 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). diff --git a/fuzz/fuzz_targets/bytecompiler-implied.rs b/fuzz/fuzz_targets/bytecompiler-implied.rs new file mode 100644 index 00000000000..dd91bbc32a3 --- /dev/null +++ b/fuzz/fuzz_targets/bytecompiler-implied.rs @@ -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) }); diff --git a/fuzz/fuzz_targets/common.rs b/fuzz/fuzz_targets/common.rs index 161a6145c00..8d4c50b5425 100644 --- a/fuzz/fuzz_targets/common.rs +++ b/fuzz/fuzz_targets/common.rs @@ -2,7 +2,7 @@ use boa_ast::{ visitor::{VisitWith, VisitorMut}, Expression, StatementList, }; -use boa_interner::{Interner, Sym}; +use boa_interner::{Interner, Sym, ToInternedString}; use libfuzzer_sys::arbitrary; use libfuzzer_sys::arbitrary::{Arbitrary, Unstructured}; use std::fmt::{Debug, Formatter}; @@ -72,3 +72,25 @@ impl Debug for FuzzData { .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 { + 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)) + } +} diff --git a/fuzz/fuzz_targets/vm-implied.rs b/fuzz/fuzz_targets/vm-implied.rs new file mode 100644 index 00000000000..77d4c14b3b9 --- /dev/null +++ b/fuzz/fuzz_targets/vm-implied.rs @@ -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 { + 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); +}); diff --git a/package-lock.json b/package-lock.json index 2ae907935ac..525d8fca0a6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,10 +9,10 @@ }, "devDependencies": { "@wasm-tool/wasm-pack-plugin": "^1.6.0", - "bootstrap": "^5.2.2", + "bootstrap": "^5.2.3", "clean-webpack-plugin": "^4.0.0", "copy-webpack-plugin": "^11.0.0", - "css-loader": "^6.7.1", + "css-loader": "^6.7.2", "file-loader": "^6.2.0", "html-webpack-plugin": "^5.5.0", "monaco-editor-webpack-plugin": "^7.0.1", @@ -20,7 +20,7 @@ "style-loader": "^3.3.1", "terser-webpack-plugin": "^5.3.6", "webpack": "^5.75.0", - "webpack-cli": "^4.10.0", + "webpack-cli": "^5.0.0", "webpack-dev-server": "^4.11.1" } }, @@ -493,34 +493,42 @@ } }, "node_modules/@webpack-cli/configtest": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-1.2.0.tgz", - "integrity": "sha512-4FB8Tj6xyVkyqjj1OaTqCjXYULB9FMkqQ8yGrZjRDrYh0nOE+7Lhs45WioWQQMV+ceFlE368Ukhe6xdvJM9Egg==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-2.0.0.tgz", + "integrity": "sha512-war4OU8NGjBqU3DP3bx6ciODXIh7dSXcpQq+P4K2Tqyd8L5OjZ7COx9QXx/QdCIwL2qoX09Wr4Cwf7uS4qdEng==", "dev": true, + "engines": { + "node": ">=14.15.0" + }, "peerDependencies": { - "webpack": "4.x.x || 5.x.x", - "webpack-cli": "4.x.x" + "webpack": "5.x.x", + "webpack-cli": "5.x.x" } }, "node_modules/@webpack-cli/info": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-1.5.0.tgz", - "integrity": "sha512-e8tSXZpw2hPl2uMJY6fsMswaok5FdlGNRTktvFk2sD8RjH0hE2+XistawJx1vmKteh4NmGmNUrp+Tb2w+udPcQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-2.0.0.tgz", + "integrity": "sha512-NNxDgbo4VOkNhOlTgY0Elhz3vKpOJq4/PKeKg7r8cmYM+GQA9vDofLYyup8jS6EpUvhNmR30cHTCEIyvXpskwA==", "dev": true, - "dependencies": { - "envinfo": "^7.7.3" + "engines": { + "node": ">=14.15.0" }, "peerDependencies": { - "webpack-cli": "4.x.x" + "webpack": "5.x.x", + "webpack-cli": "5.x.x" } }, "node_modules/@webpack-cli/serve": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.7.0.tgz", - "integrity": "sha512-oxnCNGj88fL+xzV+dacXs44HcDwf1ovs3AuEzvP7mqXw7fQntqIhQ1BRmynh4qEKQSSSRSWVyXRjmTbZIX9V2Q==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-2.0.0.tgz", + "integrity": "sha512-Rumq5mHvGXamnOh3O8yLk1sjx8dB30qF1OeR6VC00DIR6SLJ4bwwUGKC4pE7qBFoQyyh0H9sAg3fikYgAqVR0w==", "dev": true, + "engines": { + "node": ">=14.15.0" + }, "peerDependencies": { - "webpack-cli": "4.x.x" + "webpack": "5.x.x", + "webpack-cli": "5.x.x" }, "peerDependenciesMeta": { "webpack-dev-server": { @@ -774,9 +782,9 @@ "dev": true }, "node_modules/bootstrap": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.2.2.tgz", - "integrity": "sha512-dEtzMTV71n6Fhmbg4fYJzQsw1N29hJKO1js5ackCgIpDcGid2ETMGC6zwSYw09v05Y+oRdQ9loC54zB1La3hHQ==", + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.2.3.tgz", + "integrity": "sha512-cEKPM+fwb3cT8NzQZYEu4HilJ3anCrWqh3CHAok1p9jXqMPsPTBhU25fBckEJHJ/p+tTxTFTsFQGM+gaHpi3QQ==", "dev": true, "funding": [ { @@ -1167,19 +1175,19 @@ } }, "node_modules/css-loader": { - "version": "6.7.1", - "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.7.1.tgz", - "integrity": "sha512-yB5CNFa14MbPJcomwNh3wLThtkZgcNyI2bNMRt8iE5Z8Vwl7f8vQXFAzn2HDOJvtDq2NTZBUGMSUNNyrv3/+cw==", + "version": "6.7.2", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.7.2.tgz", + "integrity": "sha512-oqGbbVcBJkm8QwmnNzrFrWTnudnRZC+1eXikLJl0n4ljcfotgRifpg2a1lKy8jTrc4/d9A/ap1GFq1jDKG7J+Q==", "dev": true, "dependencies": { "icss-utils": "^5.1.0", - "postcss": "^8.4.7", + "postcss": "^8.4.18", "postcss-modules-extract-imports": "^3.0.0", "postcss-modules-local-by-default": "^4.0.0", "postcss-modules-scope": "^3.0.0", "postcss-modules-values": "^4.0.0", "postcss-value-parser": "^4.2.0", - "semver": "^7.3.5" + "semver": "^7.3.8" }, "engines": { "node": ">= 12.13.0" @@ -2310,12 +2318,12 @@ "dev": true }, "node_modules/interpret": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz", - "integrity": "sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", + "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==", "dev": true, "engines": { - "node": ">= 0.10" + "node": ">=10.13.0" } }, "node_modules/ipaddr.js": { @@ -2340,9 +2348,9 @@ } }, "node_modules/is-core-module": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.10.0.tgz", - "integrity": "sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg==", + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", + "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", "dev": true, "dependencies": { "has": "^1.0.3" @@ -2579,9 +2587,9 @@ } }, "node_modules/loader-utils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.3.tgz", - "integrity": "sha512-THWqIsn8QRnvLl0shHYVBN9syumU8pYWEHPTmkiVGd+7K5eFNVSY6AJhRvgGF70gg1Dz+l/k8WicvFCxdEs60A==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", + "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", "dev": true, "dependencies": { "big.js": "^5.2.2", @@ -3165,9 +3173,9 @@ } }, "node_modules/postcss": { - "version": "8.4.16", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.16.tgz", - "integrity": "sha512-ipHE1XBvKzm5xI7hiHCZJCSugxvsdq2mPnsq5+UF+VHCjiBvtDrlxJfMBToWaP9D5XlgNmcFGqoHmUn0EYEaRQ==", + "version": "8.4.19", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.19.tgz", + "integrity": "sha512-h+pbPsyhlYj6N2ozBmHhHrs9DzGmbaarbLvWipMRO7RLS+v4onj26MPFXA5OBYFxyqYhUJK456SwDcY9H2/zsA==", "dev": true, "funding": [ { @@ -3432,15 +3440,15 @@ } }, "node_modules/rechoir": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.7.1.tgz", - "integrity": "sha512-/njmZ8s1wVeR6pjTZ+0nCnv8SpZNRMT2D1RLOJQESlYFDBvwpTA4KWJpZ+sBJ4+vhjILRcK7JIFdGCdxEAAitg==", + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", + "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", "dev": true, "dependencies": { - "resolve": "^1.9.0" + "resolve": "^1.20.0" }, "engines": { - "node": ">= 0.10" + "node": ">= 10.13.0" } }, "node_modules/relateurl": { @@ -3636,9 +3644,9 @@ } }, "node_modules/semver": { - "version": "7.3.7", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", - "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -4347,44 +4355,42 @@ } }, "node_modules/webpack-cli": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.10.0.tgz", - "integrity": "sha512-NLhDfH/h4O6UOy+0LSso42xvYypClINuMNBVVzX4vX98TmTaTUxwRbXdhucbFMd2qLaCTcLq/PdYrvi8onw90w==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.0.0.tgz", + "integrity": "sha512-AACDTo20yG+xn6HPW5xjbn2Be4KUzQPebWXsDMHwPPyKh9OnTOJgZN2Nc+g/FZKV3ObRTYsGvibAvc+5jAUrVA==", "dev": true, "dependencies": { "@discoveryjs/json-ext": "^0.5.0", - "@webpack-cli/configtest": "^1.2.0", - "@webpack-cli/info": "^1.5.0", - "@webpack-cli/serve": "^1.7.0", + "@webpack-cli/configtest": "^2.0.0", + "@webpack-cli/info": "^2.0.0", + "@webpack-cli/serve": "^2.0.0", "colorette": "^2.0.14", - "commander": "^7.0.0", + "commander": "^9.4.1", "cross-spawn": "^7.0.3", + "envinfo": "^7.7.3", "fastest-levenshtein": "^1.0.12", "import-local": "^3.0.2", - "interpret": "^2.2.0", - "rechoir": "^0.7.0", + "interpret": "^3.1.1", + "rechoir": "^0.8.0", "webpack-merge": "^5.7.3" }, "bin": { "webpack-cli": "bin/cli.js" }, "engines": { - "node": ">=10.13.0" + "node": ">=14.15.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/webpack" }, "peerDependencies": { - "webpack": "4.x.x || 5.x.x" + "webpack": "5.x.x" }, "peerDependenciesMeta": { "@webpack-cli/generators": { "optional": true }, - "@webpack-cli/migrate": { - "optional": true - }, "webpack-bundle-analyzer": { "optional": true }, @@ -4394,12 +4400,12 @@ } }, "node_modules/webpack-cli/node_modules/commander": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", - "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "version": "9.4.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.4.1.tgz", + "integrity": "sha512-5EEkTNyHNGFPD2H+c/dXXfQZYa/scCKasxWcXJaWnNJ99pnQN9Vnmqow+p+PlFPE63Q6mThaZws1T+HxfpgtPw==", "dev": true, "engines": { - "node": ">= 10" + "node": "^12.20.0 || >=14" } }, "node_modules/webpack-dev-middleware": { @@ -5089,25 +5095,23 @@ } }, "@webpack-cli/configtest": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-1.2.0.tgz", - "integrity": "sha512-4FB8Tj6xyVkyqjj1OaTqCjXYULB9FMkqQ8yGrZjRDrYh0nOE+7Lhs45WioWQQMV+ceFlE368Ukhe6xdvJM9Egg==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-2.0.0.tgz", + "integrity": "sha512-war4OU8NGjBqU3DP3bx6ciODXIh7dSXcpQq+P4K2Tqyd8L5OjZ7COx9QXx/QdCIwL2qoX09Wr4Cwf7uS4qdEng==", "dev": true, "requires": {} }, "@webpack-cli/info": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-1.5.0.tgz", - "integrity": "sha512-e8tSXZpw2hPl2uMJY6fsMswaok5FdlGNRTktvFk2sD8RjH0hE2+XistawJx1vmKteh4NmGmNUrp+Tb2w+udPcQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-2.0.0.tgz", + "integrity": "sha512-NNxDgbo4VOkNhOlTgY0Elhz3vKpOJq4/PKeKg7r8cmYM+GQA9vDofLYyup8jS6EpUvhNmR30cHTCEIyvXpskwA==", "dev": true, - "requires": { - "envinfo": "^7.7.3" - } + "requires": {} }, "@webpack-cli/serve": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.7.0.tgz", - "integrity": "sha512-oxnCNGj88fL+xzV+dacXs44HcDwf1ovs3AuEzvP7mqXw7fQntqIhQ1BRmynh4qEKQSSSRSWVyXRjmTbZIX9V2Q==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-2.0.0.tgz", + "integrity": "sha512-Rumq5mHvGXamnOh3O8yLk1sjx8dB30qF1OeR6VC00DIR6SLJ4bwwUGKC4pE7qBFoQyyh0H9sAg3fikYgAqVR0w==", "dev": true, "requires": {} }, @@ -5299,9 +5303,9 @@ "dev": true }, "bootstrap": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.2.2.tgz", - "integrity": "sha512-dEtzMTV71n6Fhmbg4fYJzQsw1N29hJKO1js5ackCgIpDcGid2ETMGC6zwSYw09v05Y+oRdQ9loC54zB1La3hHQ==", + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.2.3.tgz", + "integrity": "sha512-cEKPM+fwb3cT8NzQZYEu4HilJ3anCrWqh3CHAok1p9jXqMPsPTBhU25fBckEJHJ/p+tTxTFTsFQGM+gaHpi3QQ==", "dev": true, "requires": {} }, @@ -5583,19 +5587,19 @@ } }, "css-loader": { - "version": "6.7.1", - "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.7.1.tgz", - "integrity": "sha512-yB5CNFa14MbPJcomwNh3wLThtkZgcNyI2bNMRt8iE5Z8Vwl7f8vQXFAzn2HDOJvtDq2NTZBUGMSUNNyrv3/+cw==", + "version": "6.7.2", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.7.2.tgz", + "integrity": "sha512-oqGbbVcBJkm8QwmnNzrFrWTnudnRZC+1eXikLJl0n4ljcfotgRifpg2a1lKy8jTrc4/d9A/ap1GFq1jDKG7J+Q==", "dev": true, "requires": { "icss-utils": "^5.1.0", - "postcss": "^8.4.7", + "postcss": "^8.4.18", "postcss-modules-extract-imports": "^3.0.0", "postcss-modules-local-by-default": "^4.0.0", "postcss-modules-scope": "^3.0.0", "postcss-modules-values": "^4.0.0", "postcss-value-parser": "^4.2.0", - "semver": "^7.3.5" + "semver": "^7.3.8" } }, "css-select": { @@ -6445,9 +6449,9 @@ "dev": true }, "interpret": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz", - "integrity": "sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", + "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==", "dev": true }, "ipaddr.js": { @@ -6466,9 +6470,9 @@ } }, "is-core-module": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.10.0.tgz", - "integrity": "sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg==", + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", + "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", "dev": true, "requires": { "has": "^1.0.3" @@ -6632,9 +6636,9 @@ "dev": true }, "loader-utils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.3.tgz", - "integrity": "sha512-THWqIsn8QRnvLl0shHYVBN9syumU8pYWEHPTmkiVGd+7K5eFNVSY6AJhRvgGF70gg1Dz+l/k8WicvFCxdEs60A==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", + "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", "dev": true, "requires": { "big.js": "^5.2.2", @@ -7073,9 +7077,9 @@ } }, "postcss": { - "version": "8.4.16", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.16.tgz", - "integrity": "sha512-ipHE1XBvKzm5xI7hiHCZJCSugxvsdq2mPnsq5+UF+VHCjiBvtDrlxJfMBToWaP9D5XlgNmcFGqoHmUn0EYEaRQ==", + "version": "8.4.19", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.19.tgz", + "integrity": "sha512-h+pbPsyhlYj6N2ozBmHhHrs9DzGmbaarbLvWipMRO7RLS+v4onj26MPFXA5OBYFxyqYhUJK456SwDcY9H2/zsA==", "dev": true, "requires": { "nanoid": "^3.3.4", @@ -7252,12 +7256,12 @@ } }, "rechoir": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.7.1.tgz", - "integrity": "sha512-/njmZ8s1wVeR6pjTZ+0nCnv8SpZNRMT2D1RLOJQESlYFDBvwpTA4KWJpZ+sBJ4+vhjILRcK7JIFdGCdxEAAitg==", + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", + "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", "dev": true, "requires": { - "resolve": "^1.9.0" + "resolve": "^1.20.0" } }, "relateurl": { @@ -7387,9 +7391,9 @@ } }, "semver": { - "version": "7.3.7", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", - "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", "dev": true, "requires": { "lru-cache": "^6.0.0" @@ -7956,29 +7960,30 @@ } }, "webpack-cli": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.10.0.tgz", - "integrity": "sha512-NLhDfH/h4O6UOy+0LSso42xvYypClINuMNBVVzX4vX98TmTaTUxwRbXdhucbFMd2qLaCTcLq/PdYrvi8onw90w==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.0.0.tgz", + "integrity": "sha512-AACDTo20yG+xn6HPW5xjbn2Be4KUzQPebWXsDMHwPPyKh9OnTOJgZN2Nc+g/FZKV3ObRTYsGvibAvc+5jAUrVA==", "dev": true, "requires": { "@discoveryjs/json-ext": "^0.5.0", - "@webpack-cli/configtest": "^1.2.0", - "@webpack-cli/info": "^1.5.0", - "@webpack-cli/serve": "^1.7.0", + "@webpack-cli/configtest": "^2.0.0", + "@webpack-cli/info": "^2.0.0", + "@webpack-cli/serve": "^2.0.0", "colorette": "^2.0.14", - "commander": "^7.0.0", + "commander": "^9.4.1", "cross-spawn": "^7.0.3", + "envinfo": "^7.7.3", "fastest-levenshtein": "^1.0.12", "import-local": "^3.0.2", - "interpret": "^2.2.0", - "rechoir": "^0.7.0", + "interpret": "^3.1.1", + "rechoir": "^0.8.0", "webpack-merge": "^5.7.3" }, "dependencies": { "commander": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", - "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "version": "9.4.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.4.1.tgz", + "integrity": "sha512-5EEkTNyHNGFPD2H+c/dXXfQZYa/scCKasxWcXJaWnNJ99pnQN9Vnmqow+p+PlFPE63Q6mThaZws1T+HxfpgtPw==", "dev": true } } diff --git a/package.json b/package.json index 9179820ca4d..7e50d48e585 100644 --- a/package.json +++ b/package.json @@ -6,10 +6,10 @@ }, "devDependencies": { "@wasm-tool/wasm-pack-plugin": "^1.6.0", - "bootstrap": "^5.2.2", + "bootstrap": "^5.2.3", "clean-webpack-plugin": "^4.0.0", "copy-webpack-plugin": "^11.0.0", - "css-loader": "^6.7.1", + "css-loader": "^6.7.2", "file-loader": "^6.2.0", "html-webpack-plugin": "^5.5.0", "monaco-editor-webpack-plugin": "^7.0.1", @@ -17,7 +17,7 @@ "style-loader": "^3.3.1", "terser-webpack-plugin": "^5.3.6", "webpack": "^5.75.0", - "webpack-cli": "^4.10.0", + "webpack-cli": "^5.0.0", "webpack-dev-server": "^4.11.1" }, "dependencies": { diff --git a/test262 b/test262 index f6c48f333e7..83a46bfe0e7 160000 --- a/test262 +++ b/test262 @@ -1 +1 @@ -Subproject commit f6c48f333e7d16bf11b30cd29b4ea9ac0354f142 +Subproject commit 83a46bfe0e79aed8274a1b9f4beb0a2efa0b3533 diff --git a/test_ignore.toml b/test_ignore.toml new file mode 100644 index 00000000000..d760c8772c4 --- /dev/null +++ b/test_ignore.toml @@ -0,0 +1,45 @@ +# Not implemented yet: +flags = ["module"] + +features = [ + # Non-implemented features: + "json-modules", + "SharedArrayBuffer", + "resizable-arraybuffer", + "Temporal", + "tail-call-optimization", + "ShadowRealm", + "FinalizationRegistry", + "Atomics", + "dynamic_import", + "decorators", + "array-grouping", + "IsHTMLDDA", + + # Non-implemented Intl features + "intl-normative-optional", + "Intl.DurationFormat", + "Intl.NumberFormat-v3", + "Intl.NumberFormat-unified", + "Intl.ListFormat", + "Intl.DisplayNames", + "Intl.RelativeTimeFormat", + "Intl.Segmenter", + "Intl.Locale", + + # Stage 3 proposals + + # https://github.com/tc39/proposal-symbols-as-weakmap-keys + "symbols-as-weakmap-keys", + + # Non-standard + "caller", + + # RegExp tests that check individual codepoints. + # They are not useful considering the cpu time they waste. + "regexp-unicode-property-escapes", +] + +# RegExp tests that check individual codepoints. +# They are not useful considering the cpu time they waste. +tests = ["CharacterClassEscapes"] diff --git a/test_ignore.txt b/test_ignore.txt deleted file mode 100644 index 523fbd39d23..00000000000 --- a/test_ignore.txt +++ /dev/null @@ -1,62 +0,0 @@ -// Not implemented yet: -flag:module - -// Non-implemented features: -feature:json-modules -feature:SharedArrayBuffer -feature:resizable-arraybuffer -feature:Temporal -feature:tail-call-optimization -feature:ShadowRealm -feature:FinalizationRegistry -feature:Atomics -feature:dynamic_import -feature:decorators -feature:array-grouping -feature:IsHTMLDDA - -// Non-implemented Intl features -feature:intl-normative-optional -feature:Intl.DurationFormat -feature:Intl.NumberFormat-v3 -feature:Intl.NumberFormat-unified -feature:Intl.ListFormat -feature:Intl.DisplayNames -feature:Intl.RelativeTimeFormat -feature:Intl.Segmenter -feature:Intl.Locale - -// Non-standard -feature:caller - -// These generate a stack overflow -tco-call -tco-member - -// RegExp tests that check individual codepoints. -// They are not usefull in comparision to the cpu time they waste. -feature:regexp-unicode-property-escapes -character-class-non-whitespace-class-escape-plus-quantifier -character-class-non-whitespace-class-escape-plus-quantifier-flags-u -character-class-non-word-class-escape-flags-u -character-class-non-whitespace-class-escape -character-class-non-digit-class-escape-plus-quantifier-flags-u -character-class-non-word-class-escape -character-class-non-whitespace-class-escape-flags-u -character-class-digit-class-escape-flags-u -character-class-digit-class-escape -character-class-non-digit-class-escape-plus-quantifier -character-class-whitespace-class-escape-flags-u -character-class-whitespace-class-escape-plus-quantifier-flags-u -character-class-word-class-escape -character-class-digit-class-escape-plus-quantifier-flags-u -character-class-non-digit-class-escape -character-class-digit-class-escape-plus-quantifier -character-class-whitespace-class-escape -character-class-whitespace-class-escape-plus-quantifier -character-class-word-class-escape-plus-quantifier -character-class-non-word-class-escape-plus-quantifier -character-class-word-class-escape-flags-u -character-class-word-class-escape-plus-quantifier-flags-u -character-class-non-digit-class-escape-flags-u -character-class-non-word-class-escape-plus-quantifier-flags-u