diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f5c47006dd6d39..532a7c1e744898 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -66,7 +66,7 @@ jobs: - name: Install rust uses: hecrj/setup-rust-action@v1 with: - rust-version: "1.43.0" + rust-version: 1.44.0 - name: Install clippy and rustfmt if: matrix.config.kind == 'lint' @@ -104,7 +104,7 @@ jobs: run: echo "::set-env name=CARGO_HOME::$(pwd)/.cargo_home" - name: Cache - uses: denoland/github-actions-cache@stable-prerelease + uses: actions/cache@v2 with: # Note: crates from the denoland/deno git repo always get rebuilt, # and their outputs ('deno', 'libdeno.rlib' etc.) are quite big, diff --git a/Cargo.lock b/Cargo.lock index 8d7391307cb888..29ff6ddb2a9230 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10,6 +10,15 @@ dependencies = [ "regex", ] +[[package]] +name = "addr2line" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a49806b9dadc843c61e7c97e72490ad7f7220ae249012fbda9ad0609457c0543" +dependencies = [ + "gimli", +] + [[package]] name = "adler32" version = "1.0.4" @@ -18,9 +27,9 @@ checksum = "5d2e7343e7fc9de883d1b0341e0b13970f764c14101234857d2ddafa1cb1cac2" [[package]] name = "ahash" -version = "0.3.2" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0989268a37e128d4d7a8028f1c60099430113fdbc70419010601ce51a228e4fe" +checksum = "2f3e0bf23f51883cce372d5d5892211236856e4bb37fb942e1eb135ee0f146e3" dependencies = [ "const-random", ] @@ -66,9 +75,9 @@ checksum = "33954243bd79057c2de7338850b85983a44588021f8a5fee574a8888c6de4344" [[package]] name = "arc-swap" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d663a8e9a99154b5fb793032533f6328da35e23aac63d5c152279aa8ba356825" +checksum = "b585a98a234c46fc563103e9278c9391fde1f4e6850334da895d27edb9580f62" [[package]] name = "arrayref" @@ -90,17 +99,17 @@ checksum = "7d96b5937e2a8b8dd9eac561c192f7fef2ab0cbc06c445e67b9e637ab158c52b" dependencies = [ "darling", "pmutil", - "proc-macro2 1.0.10", - "quote 1.0.3", + "proc-macro2 1.0.18", + "quote 1.0.6", "swc_macros_common", - "syn 1.0.17", + "syn 1.0.30", ] [[package]] name = "async-compression" -version = "0.3.2" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be3e94dc198f93aa4107649e68fbb7f4d759398643d36f498eb72a2ee8256652" +checksum = "ae84766bab9f774e32979583ba56d6af8c701288c6dc99144819d5d2ee0b170f" dependencies = [ "brotli", "bytes 0.5.4", @@ -135,26 +144,17 @@ checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" [[package]] name = "backtrace" -version = "0.3.46" +version = "0.3.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1e692897359247cc6bb902933361652380af0f1b7651ae5c5013407f30e109e" +checksum = "0df2f85c8a2abbe3b7d7e748052fdd9b76a0458fdeb16ad4223f5eca78c7c130" dependencies = [ - "backtrace-sys", + "addr2line", "cfg-if", "libc", + "object", "rustc-demangle", ] -[[package]] -name = "backtrace-sys" -version = "0.1.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7de8aba10a69c8e8d7622c5710229485ec32e9d55fdad160ea559c086fdcd118" -dependencies = [ - "cc", - "libc", -] - [[package]] name = "base64" version = "0.10.1" @@ -172,9 +172,9 @@ checksum = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7" [[package]] name = "base64" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d5ca2cd0adc3f48f9e9ea5a6bbdf9ccc0bfade884847e484d452414c7ccffb3" +checksum = "53d1ccbaf7d9ec9537465a97bf19edc1a4e158ecb49fc16178202238c569cc42" [[package]] name = "bitflags" @@ -227,9 +227,9 @@ dependencies = [ [[package]] name = "brotli-decompressor" -version = "2.3.0" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a9f2b517b96b19d8f91c1ff5b1cf498e688850b32eae5d58e02d15c4d4fdc0c" +checksum = "1052e1c3b8d4d80eb84a8b94f0a1498797b5fb96314c001156a1c761940ef4ec" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -247,9 +247,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.2.1" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12ae9db68ad7fac5fe51304d20f016c911539251075a214f8e663babefa35187" +checksum = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820" [[package]] name = "byte-tools" @@ -287,9 +287,9 @@ checksum = "5ba7d7f7b201dfcbc314b14f2176c92f8ba521dab538b40e426ffed25ed7cd80" [[package]] name = "cc" -version = "1.0.52" +version = "1.0.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d87b23d6a92cd03af510a5ade527033f6aa6fa92161e2d5863a907d4c5e31d" +checksum = "7bbb73db36c1246e9034e307d0fba23f9a2e251faa47ade70c1bd252220c8311" [[package]] name = "cfg-if" @@ -309,9 +309,9 @@ dependencies = [ [[package]] name = "clap" -version = "2.33.0" +version = "2.33.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9" +checksum = "bdfa80d47f954d53a35a64987ca1422f495b8d6483c0fe9f7117b36c2a792129" dependencies = [ "ansi_term", "atty", @@ -357,22 +357,6 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" -[[package]] -name = "core-foundation" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57d24c7a13c43e870e37c1556b74555437870a04514f7685f5b354e090567171" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "core-foundation-sys" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3a71ab494c0b5b860bdc8407ae08978052417070c2ced38573a9157ad75b8ac" - [[package]] name = "crc32fast" version = "1.2.0" @@ -403,15 +387,6 @@ dependencies = [ "lazy_static", ] -[[package]] -name = "ct-logs" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d3686f5fa27dbc1d76c751300376e167c5a43387f44bb451fd1c24776e49113" -dependencies = [ - "sct", -] - [[package]] name = "darling" version = "0.10.2" @@ -430,10 +405,10 @@ checksum = "f0c960ae2da4de88a91b2d920c2a7233b400bc33cb28453a2987822d8392519b" dependencies = [ "fnv", "ident_case", - "proc-macro2 1.0.10", - "quote 1.0.3", + "proc-macro2 1.0.18", + "quote 1.0.6", "strsim 0.9.3", - "syn 1.0.17", + "syn 1.0.30", ] [[package]] @@ -443,8 +418,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9b5a2f4ac4969822c62224815d069952656cadc7084fdca9751e6d959189b72" dependencies = [ "darling_core", - "quote 1.0.3", - "syn 1.0.17", + "quote 1.0.6", + "syn 1.0.30", ] [[package]] @@ -460,10 +435,10 @@ dependencies = [ [[package]] name = "deno" -version = "1.0.2" +version = "1.0.5" dependencies = [ "atty", - "base64 0.12.0", + "base64 0.12.1", "byteorder", "bytes 0.5.4", "clap", @@ -472,7 +447,7 @@ dependencies = [ "dirs", "dlopen", "dprint-plugin-typescript", - "futures 0.3.4", + "futures 0.3.5", "fwdansi", "glob", "http", @@ -507,17 +482,17 @@ dependencies = [ "walkdir", "warp", "webpki", - "webpki-roots 0.19.0", + "webpki-roots", "winapi 0.3.8", ] [[package]] name = "deno_core" -version = "0.45.2" +version = "0.47.1" dependencies = [ "derive_deref", "downcast-rs", - "futures 0.3.4", + "futures 0.3.5", "lazy_static", "libc", "log 0.4.8", @@ -529,7 +504,7 @@ dependencies = [ [[package]] name = "deno_typescript" -version = "0.45.2" +version = "0.47.1" dependencies = [ "deno_core", "serde", @@ -609,18 +584,18 @@ checksum = "52ba6eb47c2131e784a38b726eb54c1e1484904f013e576a25354d0124161af6" [[package]] name = "dprint-core" -version = "0.19.1" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b62634626adceb7abdab6e4dd5fc8cedcb177b2cb80a6491519092370afa037" +checksum = "e932279bfbf2e280772f1708be5502d7ff2de6a3afb388d24a38f4fdf9fe91f5" dependencies = [ "serde", ] [[package]] name = "dprint-plugin-typescript" -version = "0.18.5" +version = "0.19.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f4b628385a13a592afdd6391167921f62f1c1adc1f7b098ecf7e930ab5cc702" +checksum = "50b1a94e7824e2b41a4c7eddd39516070c283bc7f525a76fdc9230efbf2b56b5" dependencies = [ "dprint-core", "serde", @@ -644,9 +619,9 @@ checksum = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3" [[package]] name = "encoding_rs" -version = "0.8.22" +version = "0.8.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd8d03faa7fe0c1431609dfad7bbe827af30f82e1e2ae6f7ee4fca6bd764bc28" +checksum = "e8ac63f94732332f44fe654443c46f6375d1939684c17b0afb6cb56b0456e171" dependencies = [ "cfg-if", ] @@ -658,9 +633,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e57153e35187d51f08471d5840459ff29093473e7bedd004a1414985aab92f3" dependencies = [ "pmutil", - "proc-macro2 1.0.10", + "proc-macro2 1.0.18", "swc_macros_common", - "syn 1.0.17", + "syn 1.0.30", ] [[package]] @@ -676,9 +651,9 @@ dependencies = [ [[package]] name = "failure" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8529c2421efa3066a5cbd8063d2244603824daccb6936b079010bb2aa89464b" +checksum = "d32e9bd16cc02eae7db7ef620b392808b89f6a5e16bb3497d159c6b92a0f4f86" dependencies = [ "backtrace", ] @@ -691,9 +666,9 @@ checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" [[package]] name = "filetime" -version = "0.2.9" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f59efc38004c988e4201d11d263b8171f49a2e7ec0bdbb71773433f271504a5e" +checksum = "affc17579b132fc2461adf7c575cc6e8b134ebca52c51f5411388965227dc695" dependencies = [ "cfg-if", "libc", @@ -715,9 +690,9 @@ dependencies = [ [[package]] name = "fnv" -version = "1.0.6" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "from_variant" @@ -726,9 +701,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "039885ad6579a86b94ad8df696cce8c530da496bf7b07b12fec8d6c4cd654bb9" dependencies = [ "pmutil", - "proc-macro2 1.0.10", + "proc-macro2 1.0.18", "swc_macros_common", - "syn 1.0.17", + "syn 1.0.30", ] [[package]] @@ -780,9 +755,9 @@ checksum = "1b980f2816d6ee8673b6517b52cb0e808a180efc92e5c19d02cdda79066703ef" [[package]] name = "futures" -version = "0.3.4" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c329ae8753502fb44ae4fc2b622fa2a94652c41e795143765ba0927f92ab780" +checksum = "1e05b85ec287aac0dc34db7d4a569323df697f9c55b99b15d6b4ef8cde49f613" dependencies = [ "futures-channel", "futures-core", @@ -795,9 +770,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.4" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0c77d04ce8edd9cb903932b608268b3fffec4163dc053b3b402bf47eac1f1a8" +checksum = "f366ad74c28cca6ba456d95e6422883cfb4b252a83bed929c83abfdbbf2967d5" dependencies = [ "futures-core", "futures-sink", @@ -805,15 +780,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.4" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f25592f769825e89b92358db00d26f965761e094951ac44d3663ef25b7ac464a" +checksum = "59f5fff90fd5d971f936ad674802482ba441b6f09ba5e15fd8b39145582ca399" [[package]] name = "futures-executor" -version = "0.3.4" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f674f3e1bcb15b37284a90cedf55afdba482ab061c407a9c0ebbd0f3109741ba" +checksum = "10d6bb888be1153d3abeb9006b11b02cf5e9b209fda28693c31ae1e4e012e314" dependencies = [ "futures-core", "futures-task", @@ -823,39 +798,42 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.4" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a638959aa96152c7a4cddf50fcb1e3fede0583b27157c26e67d6f99904090dc6" +checksum = "de27142b013a8e869c14957e6d2edeef89e97c289e69d042ee3a49acd8b51789" [[package]] name = "futures-macro" -version = "0.3.4" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a5081aa3de1f7542a794a397cde100ed903b0630152d0973479018fd85423a7" +checksum = "d0b5a30a4328ab5473878237c447333c093297bded83a4983d10f4deea240d39" dependencies = [ "proc-macro-hack", - "proc-macro2 1.0.10", - "quote 1.0.3", - "syn 1.0.17", + "proc-macro2 1.0.18", + "quote 1.0.6", + "syn 1.0.30", ] [[package]] name = "futures-sink" -version = "0.3.4" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3466821b4bc114d95b087b850a724c6f83115e929bc88f1fa98a3304a944c8a6" +checksum = "3f2032893cb734c7a05d85ce0cc8b8c4075278e93b24b66f9de99d6eb0fa8acc" [[package]] name = "futures-task" -version = "0.3.4" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b0a34e53cf6cdcd0178aa573aed466b646eb3db769570841fda0c7ede375a27" +checksum = "bdb66b5f09e22019b1ab0830f7785bcea8e7a42148683f99214f73f8ec21a626" +dependencies = [ + "once_cell", +] [[package]] name = "futures-util" -version = "0.3.4" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22766cf25d64306bedf0384da004d05c9974ab104fcc4528f1236181c18004c5" +checksum = "8764574ff08b701a084482c3c7031349104b07ac897393010494beaa18ce32c6" dependencies = [ "futures 0.1.29", "futures-channel", @@ -865,6 +843,7 @@ dependencies = [ "futures-sink", "futures-task", "memchr", + "pin-project", "pin-utils", "proc-macro-hack", "proc-macro-nested", @@ -911,6 +890,12 @@ dependencies = [ "wasi", ] +[[package]] +name = "gimli" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcc8e0c9bce37868955864dbecd2b1ab2bdf967e6f28066d65aaac620444b65c" + [[package]] name = "glob" version = "0.3.0" @@ -919,9 +904,9 @@ checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" [[package]] name = "h2" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "377038bf3c89d18d6ca1431e7a5027194fbd724ca10592b9487ede5e8e144f42" +checksum = "79b7246d7e4b979c03fa093da39cfb3617a96bbeee6310af63991668d7e843ff" dependencies = [ "bytes 0.5.4", "fnv", @@ -942,7 +927,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed18eb2459bf1a09ad2d6b1547840c3e5e62882fa09b9a6a20b1de8e3228848f" dependencies = [ - "base64 0.12.0", + "base64 0.12.1", "bitflags", "bytes 0.5.4", "headers-core", @@ -963,9 +948,9 @@ dependencies = [ [[package]] name = "hermit-abi" -version = "0.1.10" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "725cf19794cf90aa94e65050cb4191ff5d8fa87a498383774c47b332e3af952e" +checksum = "91780f809e750b0a89f5544be56617ff6b1227ee485bcb06ebe10cdf89bd3b71" dependencies = [ "libc", ] @@ -999,9 +984,9 @@ checksum = "cd179ae861f0c2e53da70d892f5f3029f9594be0c41dc5269cd371691b1dc2f9" [[package]] name = "hyper" -version = "0.13.5" +version = "0.13.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96816e1d921eca64d208a85aab4f7798455a8e34229ee5a88c935bdee1b78b14" +checksum = "a6e7655b9594024ad0ee439f3b5a7299369dc2a3f459b47c696f9ff676f9aa1f" dependencies = [ "bytes 0.5.4", "futures-channel", @@ -1013,8 +998,8 @@ dependencies = [ "httparse", "itoa", "log 0.4.8", - "net2", "pin-project", + "socket2", "time", "tokio", "tower-service", @@ -1028,12 +1013,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac965ea399ec3a25ac7d13b8affd4b8f39325cca00858ddf5eb29b79e6b14b08" dependencies = [ "bytes 0.5.4", - "ct-logs", "futures-util", "hyper", "log 0.4.8", "rustls", - "rustls-native-certs", "tokio", "tokio-rustls", "webpki", @@ -1083,9 +1066,9 @@ checksum = "4bac95d9aa0624e7b78187d6fb8ab012b41d9f6f54b1bcb61e61c4845f8357ec" [[package]] name = "indexmap" -version = "1.3.2" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "076f042c5b7b98f31d205f1249267e12a6518c1481e9dae9764af19b707d2292" +checksum = "c398b2b113b55809ceb9ee3e753fcbac793f1956663f3c36549c1346015c2afe" dependencies = [ "autocfg 1.0.0", ] @@ -1136,9 +1119,9 @@ checksum = "b8b7a7c0c47db5545ed3fef7468ee7bb5b74691498139e4b3f6a20685dc6dd8e" [[package]] name = "js-sys" -version = "0.3.37" +version = "0.3.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a27d435371a2fa5b6d2b028a74bbdb1234f308da363226a2854ca3ff8ba7055" +checksum = "ce10c23ad2ea25ceca0093bd3192229da4c5b3c0f2de499c1ecac0d98d452177" dependencies = [ "wasm-bindgen", ] @@ -1167,9 +1150,9 @@ checksum = "b294d6fa9ee409a054354afc4352b0b9ef7ca222c69b8812cbea9e7d2bf3783f" [[package]] name = "libc" -version = "0.2.69" +version = "0.2.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99e85c08494b21a9054e7fe1374a732aeadaff3980b6990b94bfd3a70f690005" +checksum = "9457b06509d27052635f90d6466700c65095fdf75409b3fbdd903e988b886f49" [[package]] name = "lock_api" @@ -1265,9 +1248,9 @@ dependencies = [ [[package]] name = "mio" -version = "0.6.21" +version = "0.6.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "302dec22bcf6bae6dfb69c647187f4b4d0fb6f535521f7bc022430ce8e12008f" +checksum = "fce347092656428bc8eaf6201042cb551b8d67855af7374542a92a0fbfcac430" dependencies = [ "cfg-if", "fuchsia-zircon", @@ -1302,15 +1285,15 @@ checksum = "f5e374eff525ce1c5b7687c4cef63943e7686524a387933ad27ca7ec43779cb3" dependencies = [ "log 0.4.8", "mio", - "miow 0.3.3", + "miow 0.3.4", "winapi 0.3.8", ] [[package]] name = "mio-uds" -version = "0.6.7" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "966257a94e196b11bb43aca423754d87429960a768de9414f3691d6957abf125" +checksum = "afcb699eb26d4332647cc848492bbc15eafb26f08d0304550d5aa1f612e066f0" dependencies = [ "iovec", "libc", @@ -1331,9 +1314,9 @@ dependencies = [ [[package]] name = "miow" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "396aa0f2003d7df8395cb93e09871561ccc3e785f0acb369170e8cc74ddf9226" +checksum = "22dfdd1d51b2639a5abd17ed07005c3af05fb7a2a3b1a1d0d7af1000a520c1c7" dependencies = [ "socket2", "winapi 0.3.8", @@ -1359,9 +1342,9 @@ dependencies = [ [[package]] name = "net2" -version = "0.2.33" +version = "0.2.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42550d9fb7b6684a6d404d9fa7250c2eb2646df731d1c06afc06dcee9e1bcf88" +checksum = "2ba7c918ac76704fb42afcbbb43891e72731f3dcca3bef2a19786297baf14af7" dependencies = [ "cfg-if", "libc", @@ -1441,19 +1424,25 @@ dependencies = [ [[package]] name = "num_cpus" -version = "1.12.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46203554f085ff89c235cd12f7075f3233af9b11ed7c9e16dfe2560d03313ce6" +checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" dependencies = [ "hermit-abi", "libc", ] +[[package]] +name = "object" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cbca9424c482ee628fa549d9c812e2cd22f1180b9222c9200fdfa6eb31aecb2" + [[package]] name = "once_cell" -version = "1.3.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1c601810575c99596d4afc46f78a678c80105117c379eb3650cf99b8a21ce5b" +checksum = "0b631f7e854af39a1739f401cf34a8a013dfe09eac4fa4dba91e9768bd28168d" [[package]] name = "opaque-debug" @@ -1461,17 +1450,11 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" -[[package]] -name = "openssl-probe" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de" - [[package]] name = "os_pipe" -version = "0.9.1" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db4d06355a7090ce852965b2d08e11426c315438462638c6d721448d0b47aa22" +checksum = "fb233f06c2307e1f5ce2ecad9f8121cffbbee2c95428f44ea85222e460d0d213" dependencies = [ "libc", "winapi 0.3.8", @@ -1607,40 +1590,40 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7" dependencies = [ - "siphasher 0.3.2", + "siphasher 0.3.3", ] [[package]] name = "pin-project" -version = "0.4.8" +version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7804a463a8d9572f13453c516a5faea534a2403d7ced2f0c7e100eeff072772c" +checksum = "edc93aeee735e60ecb40cf740eb319ff23eab1c5748abfdb5c180e4ce49f7791" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "0.4.8" +version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "385322a45f2ecf3410c68d2a549a4a2685e8051d0f278e39743ff4e451cb9b3f" +checksum = "e58db2081ba5b4c93bd6be09c40fd36cb9193a8336c384f3b40012e531aa7e40" dependencies = [ - "proc-macro2 1.0.10", - "quote 1.0.3", - "syn 1.0.17", + "proc-macro2 1.0.18", + "quote 1.0.6", + "syn 1.0.30", ] [[package]] name = "pin-project-lite" -version = "0.1.4" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "237844750cfbb86f67afe27eee600dfbbcb6188d734139b534cbfbf4f96792ae" +checksum = "9df32da11d84f3a7d70205549562966279adb900e080fad3dccd8e64afccf0ad" [[package]] name = "pin-utils" -version = "0.1.0-alpha.4" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5894c618ce612a3fa23881b152b608bafb8c56cfc22f434a3ba3120b40f7b587" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pmutil" @@ -1648,16 +1631,16 @@ version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3894e5d549cccbe44afecf72922f277f603cd4bb0219c8342631ef18fffbe004" dependencies = [ - "proc-macro2 1.0.10", - "quote 1.0.3", - "syn 1.0.17", + "proc-macro2 1.0.18", + "quote 1.0.6", + "syn 1.0.30", ] [[package]] name = "ppv-lite86" -version = "0.2.6" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74490b50b9fbe561ac330df47c08f3f33073d2d00c150f719147d7c54522fa1b" +checksum = "237a5ed80e274dbc66f86bd59c1e25edc039660be53194b5fe0a482e0f2612ea" [[package]] name = "precomputed-hash" @@ -1667,9 +1650,9 @@ checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" [[package]] name = "proc-macro-hack" -version = "0.5.15" +version = "0.5.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d659fe7c6d27f25e9d80a1a094c223f5246f6a6596453e09d7229bf42750b63" +checksum = "7e0456befd48169b9f13ef0f0ad46d492cf9d2dbb918bcf38e01eed4ce3ec5e4" [[package]] name = "proc-macro-nested" @@ -1688,9 +1671,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.10" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df246d292ff63439fea9bc8c0a270bed0e390d5ebd4db4ba15aba81111b5abe3" +checksum = "beae6331a816b1f65d04c45b078fd8e6c93e8071771f41b8163255bbd8d7c8fa" dependencies = [ "unicode-xid 0.2.0", ] @@ -1722,11 +1705,11 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.3" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bdc6c187c65bca4260c9011c9e3132efe4909da44726bad24cf7572ae338d7f" +checksum = "54a21852a652ad6f610c9510194f398ff6f8692e334fd1145fed931f7fbe44ea" dependencies = [ - "proc-macro2 1.0.10", + "proc-macro2 1.0.18", ] [[package]] @@ -1927,9 +1910,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.3.7" +version = "1.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6020f034922e3194c711b82a627453881bc4682166cabb07134a10c26ba7692" +checksum = "9c3780fcf44b193bc4d09f36d2a3c87b251da4a046c87795a0d35f4f927ad8e6" dependencies = [ "aho-corasick", "memchr", @@ -1939,9 +1922,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.17" +version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fe5bd57d1d7414c6b5ed48563a2c855d995ff777729dcd91c369ec7fea395ae" +checksum = "26412eb97c6b088a6997e05f69403a802a92d520de2f8e63c2b65f9e0f47c4e8" [[package]] name = "remove_dir_all" @@ -1954,12 +1937,12 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.10.4" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02b81e49ddec5109a9dcfc5f2a317ff53377c915e9ae9d4f2fb50914b85614e2" +checksum = "3b82c9238b305f26f53443e3a4bc8528d64b8d0bee408ec949eb7bf5635ec680" dependencies = [ "async-compression", - "base64 0.11.0", + "base64 0.12.1", "bytes 0.5.4", "encoding_rs", "futures-core", @@ -1978,22 +1961,21 @@ dependencies = [ "rustls", "serde", "serde_urlencoded", - "time", "tokio", "tokio-rustls", "url 2.1.1", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "webpki-roots 0.18.0", + "webpki-roots", "winreg", ] [[package]] name = "ring" -version = "0.16.13" +version = "0.16.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "703516ae74571f24b465b4a1431e81e2ad51336cb0ded733a55a1aa3eccac196" +checksum = "06b3fefa4f12272808f809a0af618501fdaba41a58963c5fb72238ab0be09603" dependencies = [ "cc", "libc", @@ -2044,23 +2026,11 @@ dependencies = [ "webpki", ] -[[package]] -name = "rustls-native-certs" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75ffeb84a6bd9d014713119542ce415db3a3e4748f0bfce1e1416cd224a23a5" -dependencies = [ - "openssl-probe", - "rustls", - "schannel", - "security-framework", -] - [[package]] name = "rusty_v8" -version = "0.4.2" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acb0ad56a57c42009a8d16df5fa94ae882ad0ffe0e88fe1a23b261b3affbccf2" +checksum = "66491597ce62f02c48f0194fc9574ec0b50c648e84947400ba13882394b6d56c" dependencies = [ "bitflags", "cargo_gn", @@ -2090,9 +2060,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.3" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "535622e6be132bccd223f4bb2b8ac8d53cda3c7a6394944d3b2b33fb974f9d76" +checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" [[package]] name = "safemem" @@ -2109,16 +2079,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "schannel" -version = "0.1.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "039c25b130bd8c1321ee2d7de7fde2659fa9c2744e4bb29711cfc852ea53cd19" -dependencies = [ - "lazy_static", - "winapi 0.3.8", -] - [[package]] name = "scoped-tls" version = "1.0.0" @@ -2147,29 +2107,6 @@ dependencies = [ "untrusted", ] -[[package]] -name = "security-framework" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "572dfa3a0785509e7a44b5b4bebcf94d41ba34e9ed9eb9df722545c3b3c4144a" -dependencies = [ - "bitflags", - "core-foundation", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework-sys" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ddb15a5fec93b7021b8a9e96009c5d8d51c15673569f7c0f6b7204e5b7b404f" -dependencies = [ - "core-foundation-sys", - "libc", -] - [[package]] name = "semver" version = "0.9.0" @@ -2193,29 +2130,29 @@ checksum = "b46e1121e8180c12ff69a742aabc4f310542b6ccb69f1691689ac17fdf8618aa" [[package]] name = "serde" -version = "1.0.106" +version = "1.0.111" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36df6ac6412072f67cf767ebbde4133a5b2e88e76dc6187fa7104cd16f783399" +checksum = "c9124df5b40cbd380080b2cc6ab894c040a3070d995f5c9dc77e18c34a8ae37d" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.106" +version = "1.0.111" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e549e3abf4fb8621bd1609f11dfc9f5e50320802273b12f3811a67e6716ea6c" +checksum = "3f2c3ac8e6ca1e9c80b8be1023940162bf81ae3cffbb1809474152f2ce1eb250" dependencies = [ - "proc-macro2 1.0.10", - "quote 1.0.3", - "syn 1.0.17", + "proc-macro2 1.0.18", + "quote 1.0.6", + "syn 1.0.30", ] [[package]] name = "serde_json" -version = "1.0.52" +version = "1.0.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7894c8ed05b7a3a279aeb79025fdec1d3158080b75b98a08faf2806bb799edd" +checksum = "993948e75b189211a9b31a7528f950c6adc21f9720b6438ff80a7fa2f864cea2" dependencies = [ "indexmap", "itoa", @@ -2265,9 +2202,9 @@ checksum = "0b8de496cf83d4ed58b6be86c3a275b8602f6ffe98d3024a869e124147a9a3ac" [[package]] name = "siphasher" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e88f89a550c01e4cd809f3df4f52dc9e939f3273a2017eabd5c6d12fd98bb23" +checksum = "fa8f3741c7372e75519bd9346068370c9cdaabcc1f9599cbcf2a2719352286b7" [[package]] name = "slab" @@ -2286,9 +2223,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05720e22615919e4734f6a99ceae50d00226c3c5aca406e102ebc33298214e0a" +checksum = "c7cb5678e1615754284ec264d9bb5b4c27d2018577fd90ac0ceb578591ed5ee4" [[package]] name = "socket2" @@ -2351,8 +2288,8 @@ checksum = "f24c8e5e19d22a726626f1a5e16fe15b132dcf21d10177fa5a45ce7962996b97" dependencies = [ "phf_generator 0.8.0", "phf_shared 0.8.0", - "proc-macro2 1.0.10", - "quote 1.0.3", + "proc-macro2 1.0.18", + "quote 1.0.6", ] [[package]] @@ -2362,10 +2299,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94fdb6536756cfd35ee18b9a9972ab2a699d405cc57e0ad0532022960f30d581" dependencies = [ "pmutil", - "proc-macro2 1.0.10", - "quote 1.0.3", + "proc-macro2 1.0.18", + "quote 1.0.6", "swc_macros_common", - "syn 1.0.17", + "syn 1.0.30", ] [[package]] @@ -2429,9 +2366,9 @@ dependencies = [ [[package]] name = "swc_ecma_parser" -version = "0.23.0" +version = "0.24.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ddb997ff7671faf7e55040233ed814bed1f69b6d1ad7a0fe78948ebf42d3d6a" +checksum = "3b06bb3791cf307722bb7645f36509217649d36ac21e82493a37a8adcdce1d83" dependencies = [ "either", "enum_kind", @@ -2440,7 +2377,7 @@ dependencies = [ "once_cell", "regex", "serde", - "smallvec 1.3.0", + "smallvec 1.4.0", "swc_atoms", "swc_common", "swc_ecma_ast", @@ -2455,17 +2392,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8798810e2c79b884cf238bcb72b4bd12375121ee91724f1ceeb54b6e38a138e7" dependencies = [ "pmutil", - "proc-macro2 1.0.10", - "quote 1.0.3", + "proc-macro2 1.0.18", + "quote 1.0.6", "swc_macros_common", - "syn 1.0.17", + "syn 1.0.30", ] [[package]] name = "swc_ecma_visit" -version = "0.4.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "802b13ac5811ebc6897c9a1ed999aa9ca3dd6faf8e664b9df44e162e2b3a03ed" +checksum = "43d08204c4454eee8dcef3f5f4b9f2104abd1540740e93ebc49677777553b7b5" dependencies = [ "num-bigint", "swc_atoms", @@ -2476,16 +2413,16 @@ dependencies = [ [[package]] name = "swc_ecma_visit_macros" -version = "0.2.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9083af117bcb987631d99129ce57e634315267d84521fd562926135fa49bee38" +checksum = "1a32877a341800d772d95858c24344ce00f68361763eae5fa97187e6be136c4b" dependencies = [ "Inflector", "pmutil", - "proc-macro2 1.0.10", - "quote 1.0.3", + "proc-macro2 1.0.18", + "quote 1.0.6", "swc_macros_common", - "syn 1.0.17", + "syn 1.0.30", ] [[package]] @@ -2495,9 +2432,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "18a9f27d290938370597d363df9a77ba4be8e2bc99f32f69eb5245cdeed3c512" dependencies = [ "pmutil", - "proc-macro2 1.0.10", - "quote 1.0.3", - "syn 1.0.17", + "proc-macro2 1.0.18", + "quote 1.0.6", + "syn 1.0.30", ] [[package]] @@ -2513,20 +2450,20 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.17" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0df0eb663f387145cab623dea85b09c2c5b4b0aef44e945d928e682fce71bb03" +checksum = "93a56fabc59dce20fe48b6c832cc249c713e7ed88fa28b0ee0a3bfcaae5fe4e2" dependencies = [ - "proc-macro2 1.0.10", - "quote 1.0.3", + "proc-macro2 1.0.18", + "quote 1.0.6", "unicode-xid 0.2.0", ] [[package]] name = "sys-info" -version = "0.6.1" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb0fb3fecc8cf6ffd2b75b19f0b1e56770fc8d370fed8bb82ce90e29014951c3" +checksum = "e5cfbd84f86389198ade41b439f72a5b1b3a8ba728e61cd589e1720d0df44c39" dependencies = [ "cc", "libc", @@ -2560,7 +2497,7 @@ name = "test_plugin" version = "0.0.1" dependencies = [ "deno_core", - "futures 0.3.4", + "futures 0.3.5", ] [[package]] @@ -2583,20 +2520,19 @@ dependencies = [ [[package]] name = "time" -version = "0.1.42" +version = "0.1.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f" +checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438" dependencies = [ "libc", - "redox_syscall", "winapi 0.3.8", ] [[package]] name = "tokio" -version = "0.2.20" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05c1d570eb1a36f0345a5ce9c6c6e665b70b73d11236912c0b477616aeec47b1" +checksum = "d099fa27b9702bed751524694adbe393e18b36b204da91eb1cbbbbb4a5ee2d58" dependencies = [ "bytes 0.5.4", "fnv", @@ -2633,16 +2569,16 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0c3acc6aa564495a0f2e1d59fab677cd7f81a19994cfc7f3ad0e64301560389" dependencies = [ - "proc-macro2 1.0.10", - "quote 1.0.3", - "syn 1.0.17", + "proc-macro2 1.0.18", + "quote 1.0.6", + "syn 1.0.30", ] [[package]] name = "tokio-rustls" -version = "0.13.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4adb8b3e5f86b707f1b54e7c15b6de52617a823608ccda98a15d3a24222f265a" +checksum = "15cb62a0d2770787abc96e99c1cd98fcf17f94959f3af63ca85bdfb203f051b4" dependencies = [ "futures-core", "rustls", @@ -2656,7 +2592,7 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8b8fe88007ebc363512449868d7da4389c9400072a3f666f212c7280082882a" dependencies = [ - "futures 0.3.4", + "futures 0.3.5", "log 0.4.8", "pin-project", "tokio", @@ -2738,7 +2674,7 @@ version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" dependencies = [ - "version_check 0.9.1", + "version_check 0.9.2", ] [[package]] @@ -2756,7 +2692,7 @@ version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5479532badd04e128284890390c1e876ef7a993d0570b3597ae43dfa1d59afa4" dependencies = [ - "smallvec 1.3.0", + "smallvec 1.4.0", ] [[package]] @@ -2831,9 +2767,9 @@ checksum = "936e4b492acfd135421d8dca4b1aa80a7bfc26e702ef3af710e0752684df5372" [[package]] name = "utime" -version = "0.2.1" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "055058552ca15c566082fc61da433ae678f78986a6f16957e33162d1b218792a" +checksum = "cfab4578d925146644058fa81870b9dafd132365259758fb9e6e76b89a303494" dependencies = [ "kernel32-sys", "libc", @@ -2851,9 +2787,9 @@ dependencies = [ [[package]] name = "vec_map" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" [[package]] name = "version_check" @@ -2863,9 +2799,9 @@ checksum = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" [[package]] name = "version_check" -version = "0.9.1" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "078775d0255232fb988e6fccf26ddc9d1ac274299aaedcedce21c6f72cc533ce" +checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" [[package]] name = "void" @@ -2896,12 +2832,12 @@ dependencies = [ [[package]] name = "warp" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54cd1e2b3eb3539284d88b76a9afcf5e20f2ef2fab74db5b21a1c30d7d945e82" +checksum = "0e95175b7a927258ecbb816bdada3cc469cb68593e7940b96a60f4af366a9970" dependencies = [ "bytes 0.5.4", - "futures 0.3.4", + "futures 0.3.5", "headers", "http", "hyper", @@ -2928,9 +2864,9 @@ checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" [[package]] name = "wasm-bindgen" -version = "0.2.60" +version = "0.2.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cc57ce05287f8376e998cbddfb4c8cb43b84a7ec55cf4551d7c00eef317a47f" +checksum = "4c2dc4aa152834bc334f506c1a06b866416a8b6697d5c9f75b9a689c8486def0" dependencies = [ "cfg-if", "serde", @@ -2940,24 +2876,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.60" +version = "0.2.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d967d37bf6c16cca2973ca3af071d0a2523392e4a594548155d89a678f4237cd" +checksum = "ded84f06e0ed21499f6184df0e0cb3494727b0c5da89534e0fcc55c51d812101" dependencies = [ "bumpalo", "lazy_static", "log 0.4.8", - "proc-macro2 1.0.10", - "quote 1.0.3", - "syn 1.0.17", + "proc-macro2 1.0.18", + "quote 1.0.6", + "syn 1.0.30", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.10" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7add542ea1ac7fdaa9dc25e031a6af33b7d63376292bd24140c637d00d1c312a" +checksum = "64487204d863f109eb77e8462189d111f27cb5712cc9fdb3461297a76963a2f6" dependencies = [ "cfg-if", "js-sys", @@ -2967,38 +2903,38 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.60" +version = "0.2.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bd151b63e1ea881bb742cd20e1d6127cef28399558f3b5d415289bc41eee3a4" +checksum = "838e423688dac18d73e31edce74ddfac468e37b1506ad163ffaf0a46f703ffe3" dependencies = [ - "quote 1.0.3", + "quote 1.0.6", "wasm-bindgen-macro-support", ] [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.60" +version = "0.2.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d68a5b36eef1be7868f668632863292e37739656a80fc4b9acec7b0bd35a4931" +checksum = "3156052d8ec77142051a533cdd686cba889537b213f948cd1d20869926e68e92" dependencies = [ - "proc-macro2 1.0.10", - "quote 1.0.3", - "syn 1.0.17", + "proc-macro2 1.0.18", + "quote 1.0.6", + "syn 1.0.30", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.60" +version = "0.2.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daf76fe7d25ac79748a37538b7daeed1c7a6867c92d3245c12c6222e4a20d639" +checksum = "c9ba19973a58daf4db6f352eda73dc0e289493cd29fb2632eb172085b6521acd" [[package]] name = "web-sys" -version = "0.3.37" +version = "0.3.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d6f51648d8c56c366144378a33290049eafdd784071077f6fe37dae64c1c4cb" +checksum = "7b72fe77fd39e4bd3eaa4412fd299a0be6b3dfe9d2597e2f1c20beb968f41d17" dependencies = [ "js-sys", "wasm-bindgen", @@ -3006,23 +2942,14 @@ dependencies = [ [[package]] name = "webpki" -version = "0.21.2" +version = "0.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1f50e1972865d6b1adb54167d1c8ed48606004c2c9d0ea5f1eeb34d95e863ef" +checksum = "ab146130f5f790d45f82aeeb09e55a256573373ec64409fc19a6fb82fb1032ae" dependencies = [ "ring", "untrusted", ] -[[package]] -name = "webpki-roots" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91cd5736df7f12a964a5067a12c62fa38e1bd8080aff1f80bc29be7c80d19ab4" -dependencies = [ - "webpki", -] - [[package]] name = "webpki-roots" version = "0.19.0" @@ -3072,9 +2999,9 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa515c5163a99cc82bab70fd3bfdd36d827be85de63737b40fcef2ce084a436e" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" dependencies = [ "winapi 0.3.8", ] @@ -3087,9 +3014,9 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "winreg" -version = "0.6.2" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2986deb581c4fe11b621998a5e53361efe6b48a151178d0cd9eeffa4dc6acc9" +checksum = "0120db82e8a1e0b9fb3345a539c478767c0048d842860994d96113d5b667bd69" dependencies = [ "winapi 0.3.8", ] diff --git a/Releases.md b/Releases.md index 466790ca7d26d1..8d86ce47df351b 100644 --- a/Releases.md +++ b/Releases.md @@ -6,6 +6,95 @@ https://github.com/denoland/deno/releases We also have one-line install commands at https://github.com/denoland/deno_install +### 1.0.5 / 2020.06.03 + +Changes in the CLI: + +- fix(fetch): Support 101 status code (#6059) +- fix: REPL BorrowMutError panic (#6055) +- fix: dynamic import BorrowMutError (#6065) +- upgrade: dprint 0.19.1 and swc_ecma_parser 0.24.3 (#6068) +- upgrade: rusty_v8 0.5.0 (#6070) + +Changes in std version 0.56.0: + +- feat(std/testing): benching progress callback (#5941) +- feat(std/encoding): add base64url module (#5976) +- fix(std/testing/asserts): Format values in assertArrayContains() (#6060) + +### 1.0.4 / 2020.06.02 + +Changes in the CLI: + +- feat(core): Ops can take several zero copy buffers (#4788) +- fix(bundle): better size output (#5997) +- fix(cli): Deno.remove() fails to remove unix socket (#5967) +- fix(cli): compile TS dependencies of JS files (#6000) +- fix(cli): ES private fields parsing in SWC (#5964) +- fix(cli): Better use of @ts-expect-error (#6038) +- fix(cli): media type for .cjs and application/node (#6005) +- fix(doc): remove JSDoc comment truncation (#6031) +- fix(cli/js/web): Body.bodyUsed should use IsReadableStreamDisturbed +- fix(cli/js/web): formData parser for binary files in fetch() (#6015) +- fix(cli/js/web): set null body for null-body status in fetch() (#5980) +- fix(cli/js/web): network error on multiple redirects in fetch() (#5985) +- fix(cli/js/web): Headers.name and FormData.name (#5994) +- upgrade: Rust crates (#5959, #6032) + +Changes in std version 0.55.0: + +- feat(std/hash): add Sha512 and HmacSha512 (#6009) +- feat(std/http) support code 103 Early Hints (#6021) +- feat(std/http): add TooEarly status code (#5999) +- feat(std/io): add LimitedReader (#6026) +- feat(std/log): buffered file logging (#6014) +- feat(std/mime/multipart): Added multiple FormFile input (#6027) +- feat(std/node): add util.type.isDate (#6029) +- fix(std/http): file server not closing files (#5952) +- fix(std/path): support browsers (#6003) + +### 1.0.3 / 2020.05.29 + +Changes in the CLI: + +- fix: Add unstable checks for Deno.dir and Diagnostics (#5750) +- fix: Add unstable checks for unix transport (#5818) +- fix: Create HTTP cache lazily (#5795) +- fix: Dependency analysis in TS compiler (#5817, #5785, #5870) +- fix: Expose Error.captureStackTrace (#5254) +- fix: Improved typechecking error for unstable props (#5503) +- fix: REPL evaluates in strict mode (#5565) +- fix: Write lock file before running any code (#5794) +- fix(debugger): BorrowMutError when evaluating expression in inspector console + (#5822) +- fix(doc): Handle comments at the top of the file (#5891) +- fix(fmt): Handle formatting UTF-8 w/ BOM files (#5881) +- fix(permissions): Fix CWD and exec path leaks (#5642) +- fix(web/blob): DenoBlob name (#5879) +- fix(web/console): Hide `values` for console.table if display not necessary + (#5914) +- fix(web/console): Improve indentation when displaying objects with console.log + (#5909) +- fix(web/encoding): atob should throw dom exception (#5730) +- fix(web/fetch): Make Response constructor standard (#5787) +- fix(web/fetch): Allow ArrayBuffer as Fetch request body (#5831) +- fix(web/formData): Set default filename for Blob to (#5907) +- upgrade: dprint to 0.19.0 (#5899) + +Changes in std version 0.54.0: + +- feat(std/encoding): Add base64 (#5811) +- feat(std/http): Handle .wasm files in file_server (#5896) +- feat(std/node): Add link/linkSync polyfill (#5930) +- feat(std/node): fs.writeFile/sync path can now be an URL (#5652) +- feat(std/testing): Return results in benchmark promise (#5842) +- fix(std/http): readTrailer evaluates header names by case-insensitive (#4902) +- fix(std/log): Improve the calculation of byte length (#5819) +- fix(std/log): Fix FileHandler test with mode 'x' on non-English systems + (#5757) +- fix(std/log): Use writeAllSync instead of writeSync (#5868) +- fix(std/testing/asserts): Support browsers (#5847) + ### 1.0.2 / 2020.05.22 Changes in the CLI: diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 36e58b0e086c5c..d6176bcf60b2ce 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -2,7 +2,7 @@ [package] name = "deno" -version = "1.0.2" +version = "1.0.5" license = "MIT" authors = ["the Deno authors"] edition = "2018" @@ -15,52 +15,52 @@ name = "deno" path = "main.rs" [build-dependencies] -deno_core = { path = "../core", version = "0.45.0" } -deno_typescript = { path = "../deno_typescript", version = "0.45.0" } +deno_core = { path = "../core", version = "0.47.1" } +deno_typescript = { path = "../deno_typescript", version = "0.47.1" } [dependencies] -deno_core = { path = "../core", version = "0.45.0" } -deno_typescript = { path = "../deno_typescript", version = "0.45.0" } +deno_core = { path = "../core", version = "0.47.1" } +deno_typescript = { path = "../deno_typescript", version = "0.47.1" } atty = "0.2.14" -base64 = "0.12.0" +base64 = "0.12.1" bytes = "0.5.4" byteorder = "1.3.4" -clap = "2.33.0" +clap = "2.33.1" dirs = "2.0.2" dlopen = "0.1.8" -dprint-plugin-typescript = "0.18.5" -futures = { version = "0.3.4", features = ["compat", "io-compat"] } +dprint-plugin-typescript = "0.19.2" +futures = { version = "0.3.5", features = ["compat", "io-compat"] } glob = "0.3.0" http = "0.2.1" indexmap = "1.3.2" lazy_static = "1.4.0" -libc = "0.2.69" +libc = "0.2.71" log = "0.4.8" notify = "5.0.0-pre.2" rand = "0.7.3" -regex = "1.3.7" -reqwest = { version = "0.10.4", default-features = false, features = ["rustls-tls", "stream", "gzip", "brotli"] } -ring = "0.16.13" +regex = "1.3.9" +reqwest = { version = "0.10.6", default-features = false, features = ["rustls-tls", "stream", "gzip", "brotli"] } +ring = "0.16.14" rustyline = "6.1.2" -serde = { version = "1.0.106", features = ["derive"] } -serde_derive = "1.0.106" -serde_json = { version = "1.0.52", features = [ "preserve_order" ] } -sys-info = "0.6.1" +serde = { version = "1.0.111", features = ["derive"] } +serde_derive = "1.0.111" +serde_json = { version = "1.0.53", features = [ "preserve_order" ] } +sys-info = "0.7.0" sourcemap = "5.0.0" tempfile = "3.1.0" termcolor = "1.1.0" -tokio = { version = "0.2.20", features = ["rt-core", "tcp", "udp", "uds", "process", "fs", "blocking", "sync", "io-std", "macros", "time"] } -tokio-rustls = "0.13.0" +tokio = { version = "0.2.21", features = ["rt-core", "tcp", "udp", "uds", "process", "fs", "blocking", "sync", "io-std", "macros", "time"] } +tokio-rustls = "0.13.1" url = "2.1.1" -utime = "0.2.1" -webpki = "0.21.2" +utime = "0.3.0" +webpki = "0.21.3" webpki-roots = "0.19.0" walkdir = "2.3.1" -warp = "0.2.2" +warp = "0.2.3" semver-parser = "0.9.0" uuid = { version = "0.8.1", features = ["v4"] } -swc_ecma_visit = "0.4.0" +swc_ecma_visit = "0.5.1" [target.'cfg(windows)'.dependencies] winapi = "0.3.8" @@ -70,7 +70,7 @@ fwdansi = "1.1.0" nix = "0.17.0" [dev-dependencies] -os_pipe = "0.9.1" +os_pipe = "0.9.2" # Used for testing inspector. Keep in-sync with warp. tokio-tungstenite = { version = "0.10.1", features = ["connect"] } diff --git a/cli/doc/node.rs b/cli/doc/node.rs index 0846db0dd82a00..0f97ed65abe734 100644 --- a/cli/doc/node.rs +++ b/cli/doc/node.rs @@ -32,7 +32,7 @@ pub struct ParamDef { pub ts_type: Option, } -#[derive(Debug, Serialize, Clone)] +#[derive(Debug, Serialize, Clone, PartialEq)] pub struct Location { pub filename: String, pub line: usize, diff --git a/cli/doc/parser.rs b/cli/doc/parser.rs index 9215637c50ac5b..3746e3dfba0c32 100644 --- a/cli/doc/parser.rs +++ b/cli/doc/parser.rs @@ -530,7 +530,7 @@ impl DocParser { pub fn js_doc_for_span(&self, span: Span) -> Option { let comments = self.ast_parser.get_span_comments(span); - let js_doc_comment = comments.iter().find(|comment| { + let js_doc_comment = comments.iter().rev().find(|comment| { comment.kind == CommentKind::Block && comment.text.starts_with('*') })?; diff --git a/cli/doc/printer.rs b/cli/doc/printer.rs index f7f41079db349d..d24e659015f806 100644 --- a/cli/doc/printer.rs +++ b/cli/doc/printer.rs @@ -35,12 +35,13 @@ pub fn format_details(node: doc::DocNode) -> String { let js_doc = node.js_doc.clone(); if let Some(js_doc) = js_doc { - details.push_str(&format_jsdoc(js_doc, false, 1)); + details.push_str(&format_jsdoc(js_doc, 1)); } details.push_str("\n"); let maybe_extra = match node.kind { DocNodeKind::Class => Some(format_class_details(node)), + DocNodeKind::Enum => Some(format_enum_details(node)), DocNodeKind::Namespace => Some(format_namespace_details(node)), _ => None, }; @@ -92,7 +93,7 @@ fn format_(doc_nodes: Vec, indent: i64) -> String { for node in sorted { output.push_str(&format_signature(&node, indent)); if let Some(js_doc) = node.js_doc { - output.push_str(&format_jsdoc(js_doc, true, indent)); + output.push_str(&format_jsdoc(js_doc, indent)); } output.push_str("\n"); if DocNodeKind::Namespace == node.kind { @@ -308,19 +309,15 @@ fn add_indent(string: String, indent: i64) -> String { } // TODO: this should use some sort of markdown to console parser. -fn format_jsdoc(jsdoc: String, truncated: bool, indent: i64) -> String { - let mut lines = jsdoc.split("\n\n").map(|line| line.replace("\n", " ")); +fn format_jsdoc(jsdoc: String, indent: i64) -> String { + let lines = jsdoc.split("\n\n").map(|line| line.replace("\n", " ")); let mut js_doc = String::new(); - if truncated { - let first_line = lines.next().unwrap_or_else(|| "".to_string()); - js_doc.push_str(&add_indent(format!("{}\n", first_line), indent + 1)); - } else { - for line in lines { - js_doc.push_str(&add_indent(format!("{}\n", line), indent + 1)); - } + for line in lines { + js_doc.push_str(&add_indent(format!("{}\n", line), indent + 1)); } + format!("{}", colors::gray(js_doc)) } @@ -416,6 +413,17 @@ fn format_class_details(node: doc::DocNode) -> String { details } +fn format_enum_details(node: doc::DocNode) -> String { + let mut details = String::new(); + let enum_def = node.enum_def.unwrap(); + for member in enum_def.members { + details + .push_str(&add_indent(format!("{}\n", colors::bold(member.name)), 1)); + } + details.push_str("\n"); + details +} + fn format_namespace_details(node: doc::DocNode) -> String { let mut ns = String::new(); diff --git a/cli/doc/tests.rs b/cli/doc/tests.rs index 7f2c1863962bee..f3355a520c579c 100644 --- a/cli/doc/tests.rs +++ b/cli/doc/tests.rs @@ -44,6 +44,10 @@ impl DocFileLoader for TestLoader { #[tokio::test] async fn export_fn() { let source_code = r#"/** +* @module foo +*/ + +/** * Hello there, this is a multiline JSdoc. * * It has many lines @@ -51,6 +55,9 @@ async fn export_fn() { * Or not that many? */ export function foo(a: string, b?: number, cb: (...cbArgs: unknown[]) => void, ...args: unknown[]): void { + /** + * @todo document all the things. + */ console.log("Hello world"); } "#; @@ -143,7 +150,7 @@ export function foo(a: string, b?: number, cb: (...cbArgs: unknown[]) => void, . "location": { "col": 0, "filename": "test.ts", - "line": 8, + "line": 12, }, "name": "foo", }); @@ -877,6 +884,10 @@ export enum Hello { let actual = serde_json::to_value(entry).unwrap(); assert_eq!(actual, expected_json); + assert!(colors::strip_ansi_codes( + super::printer::format_details(entry.clone()).as_str() + ) + .contains("World")); assert!(colors::strip_ansi_codes( super::printer::format(entries.clone()).as_str() ) diff --git a/cli/file_fetcher.rs b/cli/file_fetcher.rs index 375142e0714476..be3bc6efc67053 100644 --- a/cli/file_fetcher.rs +++ b/cli/file_fetcher.rs @@ -544,6 +544,7 @@ pub fn map_file_extension(path: &Path) -> msg::MediaType { Some("js") => msg::MediaType::JavaScript, Some("jsx") => msg::MediaType::JSX, Some("mjs") => msg::MediaType::JavaScript, + Some("cjs") => msg::MediaType::JavaScript, Some("json") => msg::MediaType::Json, Some("wasm") => msg::MediaType::Wasm, _ => msg::MediaType::Unknown, @@ -572,7 +573,8 @@ fn map_content_type(path: &Path, content_type: Option<&str>) -> msg::MediaType { | "text/javascript" | "application/ecmascript" | "text/ecmascript" - | "application/x-javascript" => { + | "application/x-javascript" + | "application/node" => { map_js_like_extension(path, msg::MediaType::JavaScript) } "application/json" | "text/json" => msg::MediaType::Json, @@ -1596,6 +1598,10 @@ mod tests { map_file_extension(Path::new("foo/bar.wasm")), msg::MediaType::Wasm ); + assert_eq!( + map_file_extension(Path::new("foo/bar.cjs")), + msg::MediaType::JavaScript + ); assert_eq!( map_file_extension(Path::new("foo/bar.txt")), msg::MediaType::Unknown @@ -1641,6 +1647,10 @@ mod tests { map_content_type(Path::new("foo/bar.wasm"), None), msg::MediaType::Wasm ); + assert_eq!( + map_content_type(Path::new("foo/bar.cjs"), None), + msg::MediaType::JavaScript + ); assert_eq!( map_content_type(Path::new("foo/bar"), None), msg::MediaType::Unknown @@ -1694,6 +1704,10 @@ mod tests { map_content_type(Path::new("foo/bar"), Some("application/json")), msg::MediaType::Json ); + assert_eq!( + map_content_type(Path::new("foo/bar"), Some("application/node")), + msg::MediaType::JavaScript + ); assert_eq!( map_content_type(Path::new("foo/bar"), Some("text/json")), msg::MediaType::Json diff --git a/cli/flags.rs b/cli/flags.rs index 075a29b5d475a0..1eebb8b87f01c9 100644 --- a/cli/flags.rs +++ b/cli/flags.rs @@ -1,5 +1,4 @@ // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. -use crate::fs::resolve_from_cwd; use clap::App; use clap::AppSettings; use clap::Arg; @@ -7,7 +6,7 @@ use clap::ArgMatches; use clap::SubCommand; use log::Level; use std::net::SocketAddr; -use std::path::{Path, PathBuf}; +use std::path::PathBuf; /// Creates vector of strings, Vec macro_rules! svec { @@ -472,13 +471,6 @@ fn lock_args_parse(flags: &mut Flags, matches: &clap::ArgMatches) { } } -fn resolve_fs_whitelist(whitelist: &[PathBuf]) -> Vec { - whitelist - .iter() - .map(|raw_path| resolve_from_cwd(Path::new(&raw_path)).unwrap()) - .collect() -} - // Shared between the run and test subcommands. They both take similar options. fn run_test_args_parse(flags: &mut Flags, matches: &clap::ArgMatches) { reload_arg_parse(flags, matches); @@ -1235,25 +1227,22 @@ fn no_remote_arg_parse(flags: &mut Flags, matches: &clap::ArgMatches) { fn permission_args_parse(flags: &mut Flags, matches: &clap::ArgMatches) { if let Some(read_wl) = matches.values_of("allow-read") { - let raw_read_whitelist: Vec = read_wl.map(PathBuf::from).collect(); + let read_whitelist: Vec = read_wl.map(PathBuf::from).collect(); - if raw_read_whitelist.is_empty() { + if read_whitelist.is_empty() { flags.allow_read = true; } else { - flags.read_whitelist = resolve_fs_whitelist(&raw_read_whitelist); - debug!("read whitelist: {:#?}", &flags.read_whitelist); + flags.read_whitelist = read_whitelist; } } if let Some(write_wl) = matches.values_of("allow-write") { - let raw_write_whitelist: Vec = - write_wl.map(PathBuf::from).collect(); + let write_whitelist: Vec = write_wl.map(PathBuf::from).collect(); - if raw_write_whitelist.is_empty() { + if write_whitelist.is_empty() { flags.allow_write = true; } else { - flags.write_whitelist = resolve_fs_whitelist(&raw_write_whitelist); - debug!("write whitelist: {:#?}", &flags.write_whitelist); + flags.write_whitelist = write_whitelist; } } @@ -1352,7 +1341,6 @@ fn resolve_hosts(paths: Vec) -> Vec { #[cfg(test)] mod tests { use super::*; - use std::env::current_dir; #[test] fn upgrade() { @@ -1853,7 +1841,7 @@ mod tests { r.unwrap(), Flags { allow_read: false, - read_whitelist: vec![current_dir().unwrap(), temp_dir], + read_whitelist: vec![PathBuf::from("."), temp_dir], subcommand: DenoSubcommand::Run { script: "script.ts".to_string(), }, @@ -1877,7 +1865,7 @@ mod tests { r.unwrap(), Flags { allow_write: false, - write_whitelist: vec![current_dir().unwrap(), temp_dir], + write_whitelist: vec![PathBuf::from("."), temp_dir], subcommand: DenoSubcommand::Run { script: "script.ts".to_string(), }, diff --git a/cli/fmt.rs b/cli/fmt.rs index 9b6e5975ddb079..d3f230ae20d215 100644 --- a/cli/fmt.rs +++ b/cli/fmt.rs @@ -21,6 +21,8 @@ use std::path::PathBuf; use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::{Arc, Mutex}; +const BOM_CHAR: char = '\u{FEFF}'; + /// Format JavaScript/TypeScript files. /// /// First argument supports globs, and if it is `None` @@ -66,11 +68,11 @@ async fn check_source_files( run_parallelized(paths, { let not_formatted_files_count = not_formatted_files_count.clone(); move |file_path| { - let file_contents = fs::read_to_string(&file_path)?; - let r = formatter.format_text(&file_path, &file_contents); + let file_text = read_file_contents(&file_path)?.text; + let r = formatter.format_text(&file_path, &file_text); match r { Ok(formatted_text) => { - if formatted_text != file_contents { + if formatted_text != file_text { not_formatted_files_count.fetch_add(1, Ordering::SeqCst); } } @@ -112,12 +114,18 @@ async fn format_source_files( run_parallelized(paths, { let formatted_files_count = formatted_files_count.clone(); move |file_path| { - let file_contents = fs::read_to_string(&file_path)?; - let r = formatter.format_text(&file_path, &file_contents); + let file_contents = read_file_contents(&file_path)?; + let r = formatter.format_text(&file_path, &file_contents.text); match r { Ok(formatted_text) => { - if formatted_text != file_contents { - fs::write(&file_path, formatted_text)?; + if formatted_text != file_contents.text { + write_file_contents( + &file_path, + FileContents { + had_bom: file_contents.had_bom, + text: formatted_text, + }, + )?; formatted_files_count.fetch_add(1, Ordering::SeqCst); let _g = output_lock.lock().unwrap(); println!("{}", file_path.to_string_lossy()); @@ -171,13 +179,6 @@ fn format_stdin(check: bool) -> Result<(), ErrBox> { Ok(()) } -/// Formats the given source text -pub fn format_text(source: &str) -> Result { - dprint::Formatter::new(get_config()) - .format_text(&PathBuf::from("_tmp.ts"), &source) - .map_err(|e| OpError::other(e).into()) -} - fn files_str(len: usize) -> &'static str { if len == 1 { "file" @@ -203,6 +204,38 @@ fn get_config() -> dprint::configuration::Configuration { ConfigurationBuilder::new().deno().build() } +struct FileContents { + text: String, + had_bom: bool, +} + +fn read_file_contents(file_path: &PathBuf) -> Result { + let file_text = fs::read_to_string(&file_path)?; + let had_bom = file_text.starts_with(BOM_CHAR); + let text = if had_bom { + // remove the BOM + String::from(&file_text[BOM_CHAR.len_utf8()..]) + } else { + file_text + }; + + Ok(FileContents { text, had_bom }) +} + +fn write_file_contents( + file_path: &PathBuf, + file_contents: FileContents, +) -> Result<(), ErrBox> { + let file_text = if file_contents.had_bom { + // add back the BOM + format!("{}{}", BOM_CHAR, file_contents.text) + } else { + file_contents.text + }; + + Ok(fs::write(file_path, file_text)?) +} + async fn run_parallelized( file_paths: Vec, f: F, diff --git a/cli/global_state.rs b/cli/global_state.rs index 8bd01f438fa9aa..c1ed3af61fff33 100644 --- a/cli/global_state.rs +++ b/cli/global_state.rs @@ -3,9 +3,14 @@ use crate::deno_dir; use crate::file_fetcher::SourceFileFetcher; use crate::flags; use crate::http_cache; +use crate::import_map::ImportMap; use crate::lockfile::Lockfile; +use crate::module_graph::ModuleGraphFile; +use crate::module_graph::ModuleGraphLoader; use crate::msg; +use crate::msg::MediaType; use crate::permissions::Permissions; +use crate::state::exit_unstable; use crate::tsc::CompiledModule; use crate::tsc::TargetLib; use crate::tsc::TsCompiler; @@ -35,6 +40,7 @@ pub struct GlobalStateInner { pub ts_compiler: TsCompiler, pub lockfile: Option>, pub compiler_starts: AtomicUsize, + pub maybe_import_map: Option, compile_lock: AsyncMutex<()>, } @@ -75,6 +81,17 @@ impl GlobalState { None }; + let maybe_import_map: Option = + match flags.import_map_path.as_ref() { + None => None, + Some(file_path) => { + if !flags.unstable { + exit_unstable("--importmap") + } + Some(ImportMap::load(file_path)?) + } + }; + let inner = GlobalStateInner { dir, permissions: Permissions::from_flags(&flags), @@ -82,19 +99,83 @@ impl GlobalState { file_fetcher, ts_compiler, lockfile, + maybe_import_map, compiler_starts: AtomicUsize::new(0), compile_lock: AsyncMutex::new(()), }; Ok(GlobalState(Arc::new(inner))) } - pub async fn fetch_compiled_module( + /// This function is called when new module load is + /// initialized by the EsIsolate. Its resposibility is to collect + /// all dependencies and if it is required then also perform TS typecheck + /// and traspilation. + pub async fn prepare_module_load( &self, module_specifier: ModuleSpecifier, maybe_referrer: Option, target_lib: TargetLib, permissions: Permissions, is_dyn_import: bool, + maybe_import_map: Option, + ) -> Result<(), ErrBox> { + let module_specifier = module_specifier.clone(); + + // TODO(ry) Try to lift compile_lock as high up in the call stack for + // sanity. + let compile_lock = self.compile_lock.lock().await; + + let mut module_graph_loader = ModuleGraphLoader::new( + self.file_fetcher.clone(), + maybe_import_map, + permissions.clone(), + is_dyn_import, + false, + ); + module_graph_loader + .add_to_graph(&module_specifier, maybe_referrer) + .await?; + let module_graph = module_graph_loader.get_graph(); + + let out = self + .file_fetcher + .fetch_cached_source_file(&module_specifier, permissions.clone()) + .expect("Source file not found"); + + // Check if we need to compile files. + let should_compile = needs_compilation( + self.ts_compiler.compile_js, + out.media_type, + module_graph.values().collect::>(), + ); + + if should_compile { + self + .ts_compiler + .compile_module_graph( + self.clone(), + &out, + target_lib, + permissions, + module_graph, + ) + .await?; + } + + drop(compile_lock); + + Ok(()) + } + + // TODO(bartlomieju): this method doesn't need to be async anymore + /// This method is used after `prepare_module_load` finishes and EsIsolate + /// starts loading source and executing source code. This method shouldn't + /// perform any IO (besides $DENO_DIR) and only operate on sources collected + /// during `prepare_module_load`. + pub async fn fetch_compiled_module( + &self, + module_specifier: ModuleSpecifier, + _maybe_referrer: Option, ) -> Result { let state1 = self.clone(); let state2 = self.clone(); @@ -102,59 +183,31 @@ impl GlobalState { let out = self .file_fetcher - .fetch_source_file(&module_specifier, maybe_referrer, permissions.clone()) - .await?; + .fetch_cached_source_file(&module_specifier, Permissions::allow_all()) + .expect("Cached source file doesn't exist"); // TODO(ry) Try to lift compile_lock as high up in the call stack for // sanity. let compile_lock = self.compile_lock.lock().await; - let compiled_module = match out.media_type { + // Check if we need to compile files + let was_compiled = match out.media_type { msg::MediaType::TypeScript | msg::MediaType::TSX - | msg::MediaType::JSX => { - state1 - .ts_compiler - .compile(state1.clone(), &out, target_lib, permissions, is_dyn_import) - .await - } - msg::MediaType::JavaScript => { - if state1.ts_compiler.compile_js { - state2 - .ts_compiler - .compile( - state1.clone(), - &out, - target_lib, - permissions, - is_dyn_import, - ) - .await - } else { - if let Some(types_url) = out.types_url.clone() { - let types_specifier = ModuleSpecifier::from(types_url); - state1 - .file_fetcher - .fetch_source_file( - &types_specifier, - Some(module_specifier.clone()), - permissions.clone(), - ) - .await - .ok(); - }; - - Ok(CompiledModule { - code: String::from_utf8(out.source_code.clone())?, - name: out.url.to_string(), - }) - } - } - _ => Ok(CompiledModule { + | msg::MediaType::JSX => true, + msg::MediaType::JavaScript => self.ts_compiler.compile_js, + _ => false, + }; + + let compiled_module = if was_compiled { + state1.ts_compiler.get_compiled_module(&out.url)? + } else { + CompiledModule { code: String::from_utf8(out.source_code.clone())?, name: out.url.to_string(), - }), - }?; + } + }; + drop(compile_lock); if let Some(ref lockfile) = state2.lockfile { @@ -188,6 +241,34 @@ impl GlobalState { } } +// Compilation happens if either: +// - `checkJs` is set to true in TS config +// - entry point is a TS file +// - any dependency in module graph is a TS file +fn needs_compilation( + compile_js: bool, + media_type: MediaType, + module_graph_files: Vec<&ModuleGraphFile>, +) -> bool { + let mut needs_compilation = match media_type { + msg::MediaType::TypeScript | msg::MediaType::TSX | msg::MediaType::JSX => { + true + } + msg::MediaType::JavaScript => compile_js, + _ => false, + }; + + needs_compilation |= module_graph_files.iter().any(|module_file| { + let media_type = module_file.media_type; + + media_type == (MediaType::TypeScript as i32) + || media_type == (MediaType::TSX as i32) + || media_type == (MediaType::JSX as i32) + }); + + needs_compilation +} + #[test] fn thread_safe() { fn f(_: S) {} @@ -195,9 +276,44 @@ fn thread_safe() { } #[test] -fn import_map_given_for_repl() { - let _result = GlobalState::new(flags::Flags { - import_map_path: Some("import_map.json".to_string()), - ..flags::Flags::default() - }); +fn test_needs_compilation() { + assert!(!needs_compilation( + false, + MediaType::JavaScript, + vec![&ModuleGraphFile { + specifier: "some/file.js".to_string(), + url: "file:///some/file.js".to_string(), + redirect: None, + filename: "some/file.js".to_string(), + imports: vec![], + referenced_files: vec![], + lib_directives: vec![], + types_directives: vec![], + type_headers: vec![], + media_type: MediaType::JavaScript as i32, + source_code: "function foo() {}".to_string(), + }] + )); + assert!(!needs_compilation(false, MediaType::JavaScript, vec![])); + assert!(needs_compilation(true, MediaType::JavaScript, vec![])); + assert!(needs_compilation(false, MediaType::TypeScript, vec![])); + assert!(needs_compilation(false, MediaType::JSX, vec![])); + assert!(needs_compilation(false, MediaType::TSX, vec![])); + assert!(needs_compilation( + false, + MediaType::JavaScript, + vec![&ModuleGraphFile { + specifier: "some/file.ts".to_string(), + url: "file:///some/file.ts".to_string(), + redirect: None, + filename: "some/file.ts".to_string(), + imports: vec![], + referenced_files: vec![], + lib_directives: vec![], + types_directives: vec![], + type_headers: vec![], + media_type: MediaType::TypeScript as i32, + source_code: "function foo() {}".to_string(), + }] + )); } diff --git a/cli/inspector.rs b/cli/inspector.rs index b67bb89f5a1c0e..94114addb47611 100644 --- a/cli/inspector.rs +++ b/cli/inspector.rs @@ -375,14 +375,10 @@ impl DenoInspector { isolate: &mut deno_core::CoreIsolate, host: SocketAddr, ) -> Box { - let deno_core::CoreIsolate { - v8_isolate, - global_context, - .. - } = isolate; - - let v8_isolate = v8_isolate.as_mut().unwrap(); - let mut hs = v8::HandleScope::new(v8_isolate); + let core_state_rc = deno_core::CoreIsolate::state(isolate); + let core_state = core_state_rc.borrow(); + + let mut hs = v8::HandleScope::new(isolate); let scope = hs.enter(); let (new_websocket_tx, new_websocket_rx) = @@ -420,7 +416,7 @@ impl DenoInspector { }); // Tell the inspector about the global context. - let context = global_context.get(scope).unwrap(); + let context = core_state.global_context.get(scope).unwrap(); let context_name = v8::inspector::StringView::from(&b"global context"[..]); self_.context_created(context, Self::CONTEXT_GROUP_ID, context_name); diff --git a/cli/js/diagnostics_util.ts b/cli/js/diagnostics_util.ts index 17e73e377da212..7b66d72a39557e 100644 --- a/cli/js/diagnostics_util.ts +++ b/cli/js/diagnostics_util.ts @@ -10,6 +10,79 @@ import { DiagnosticItem, } from "./diagnostics.ts"; +const unstableDenoGlobalProperties = [ + "umask", + "linkSync", + "link", + "symlinkSync", + "symlink", + "DirKind", + "dir", + "loadavg", + "osRelease", + "openPlugin", + "DiagnosticCategory", + "DiagnosticMessageChain", + "DiagnosticItem", + "Diagnostic", + "formatDiagnostics", + "CompilerOptions", + "TranspileOnlyResult", + "transpileOnly", + "compile", + "bundle", + "Location", + "applySourceMap", + "LinuxSignal", + "MacOSSignal", + "Signal", + "SignalStream", + "signal", + "signals", + "setRaw", + "utimeSync", + "utime", + "ShutdownMode", + "shutdown", + "DatagramConn", + "UnixListenOptions", + "listen", + "listenDatagram", + "UnixConnectOptions", + "connect", + "StartTlsOptions", + "startTls", + "kill", + "PermissionName", + "PermissionState", + "RunPermissionDescriptor", + "ReadPermissionDescriptor", + "WritePermissionDescriptor", + "NetPermissionDescriptor", + "EnvPermissionDescriptor", + "PluginPermissionDescriptor", + "HrtimePermissionDescriptor", + "PermissionDescriptor", + "Permissions", + "PermissionStatus", + "hostname", +]; + +function transformMessageText(messageText: string, code: number): string { + if (code === 2339) { + const property = messageText + .replace(/^Property '/, "") + .replace(/' does not exist on type 'typeof Deno'\.$/, ""); + if ( + messageText.endsWith("on type 'typeof Deno'.") && + unstableDenoGlobalProperties.includes(property) + ) { + return `${messageText} 'Deno.${property}' is an unstable API. Did you forget to run with the '--unstable' flag?`; + } + } + return messageText; +} + interface SourceInformation { sourceLine: string; lineNumber: number; @@ -78,7 +151,8 @@ function fromDiagnosticMessageChain( return undefined; } - return messageChain.map(({ messageText: message, code, category, next }) => { + return messageChain.map(({ messageText, code, category, next }) => { + const message = transformMessageText(messageText, code); return { message, code, @@ -110,9 +184,9 @@ function parseDiagnostic( let message: string; let messageChain: DiagnosticMessageChain | undefined; if (typeof messageText === "string") { - message = messageText; + message = transformMessageText(messageText, code); } else { - message = messageText.messageText; + message = transformMessageText(messageText.messageText, messageText.code); messageChain = fromDiagnosticMessageChain([messageText])![0]; } diff --git a/cli/js/error_stack.ts b/cli/js/error_stack.ts index 8a3d0530b05bf3..fb07b6f41053d4 100644 --- a/cli/js/error_stack.ts +++ b/cli/js/error_stack.ts @@ -119,7 +119,7 @@ function getFileLocation(callSite: CallSite, isInternal = false): string { const lineNumber = callSite.getLineNumber(); if (lineNumber != null) { - result += `${black(":")}${yellow(lineNumber.toString())}`; + result += `${black("#")}${yellow(lineNumber.toString())}`; const columnNumber = callSite.getColumnNumber(); if (columnNumber != null) { @@ -215,50 +215,47 @@ function evaluateCallSite(callSite: CallSite): CallSiteEval { } function prepareStackTrace( - error: Error, - structuredStackTrace: CallSite[] + error: Error & { + __callSiteEvals: CallSiteEval[]; + __formattedFrames: string[]; + }, + callSites: CallSite[] ): string { + const mappedCallSites = callSites.map( + (callSite): CallSite => { + const fileName = callSite.getFileName(); + const lineNumber = callSite.getLineNumber(); + const columnNumber = callSite.getColumnNumber(); + if (fileName && lineNumber != null && columnNumber != null) { + return patchCallSite( + callSite, + applySourceMap({ + fileName, + lineNumber, + columnNumber, + }) + ); + } + return callSite; + } + ); Object.defineProperties(error, { - __callSiteEvals: { value: [] }, - __formattedFrames: { value: [] }, + __callSiteEvals: { value: [], configurable: true }, + __formattedFrames: { value: [], configurable: true }, }); - const errorString = - `${error.name}: ${error.message}\n` + - structuredStackTrace - .map( - (callSite): CallSite => { - const fileName = callSite.getFileName(); - const lineNumber = callSite.getLineNumber(); - const columnNumber = callSite.getColumnNumber(); - if (fileName && lineNumber != null && columnNumber != null) { - return patchCallSite( - callSite, - applySourceMap({ - fileName, - lineNumber, - columnNumber, - }) - ); - } - return callSite; - } - ) - .map((callSite): string => { - // @ts-expect-error - error.__callSiteEvals.push(Object.freeze(evaluateCallSite(callSite))); - const isInternal = - callSite.getFileName()?.startsWith("$deno$") ?? false; - const string = callSiteToString(callSite, isInternal); - // @ts-expect-error - error.__formattedFrames.push(string); - return ` at ${colors.stripColor(string)}`; - }) - .join("\n"); - // @ts-expect-error + for (const callSite of mappedCallSites) { + error.__callSiteEvals.push(Object.freeze(evaluateCallSite(callSite))); + const isInternal = callSite.getFileName()?.startsWith("$deno$") ?? false; + error.__formattedFrames.push(callSiteToString(callSite, isInternal)); + } Object.freeze(error.__callSiteEvals); - // @ts-expect-error Object.freeze(error.__formattedFrames); - return errorString; + return ( + `${error.name}: ${error.message}\n` + + error.__formattedFrames + .map((s: string) => ` at ${colors.stripColor(s)}`) + .join("\n") + ); } // @internal diff --git a/cli/js/globals.ts b/cli/js/globals.ts index 9908609be84443..09be63315b44a4 100644 --- a/cli/js/globals.ts +++ b/cli/js/globals.ts @@ -80,7 +80,7 @@ declare global { dispatch( opId: number, control: Uint8Array, - zeroCopy?: ArrayBufferView | null + ...zeroCopy: ArrayBufferView[] ): Uint8Array | null; setAsyncHandler(opId: number, cb: (msg: Uint8Array) => void): void; sharedQueue: { @@ -99,7 +99,7 @@ declare global { send( opId: number, control: null | ArrayBufferView, - data?: ArrayBufferView + ...data: ArrayBufferView[] ): null | Uint8Array; setMacrotaskCallback(cb: () => boolean): void; diff --git a/cli/js/lib.deno.ns.d.ts b/cli/js/lib.deno.ns.d.ts index fd0c0e6d73f323..be4b1f15dd6812 100644 --- a/cli/js/lib.deno.ns.d.ts +++ b/cli/js/lib.deno.ns.d.ts @@ -364,7 +364,7 @@ declare namespace Deno { * * ```ts * let f = Deno.openSync("/etc/passwd"); - * for (const chunk of Deno.iterSync(reader)) { + * for (const chunk of Deno.iterSync(f)) { * console.log(chunk); * } * f.close(); @@ -509,7 +509,7 @@ declare namespace Deno { * ```ts * const encoder = new TextEncoder(); * const data = encoder.encode("Hello world"); - * const file = Deno.openSync("/foo/bar.txt"); + * const file = Deno.openSync("/foo/bar.txt", {write: true}); * const bytesWritten = Deno.writeSync(file.rid, data); // 11 * Deno.close(file.rid); * ``` @@ -528,7 +528,7 @@ declare namespace Deno { * ```ts * const encoder = new TextEncoder(); * const data = encoder.encode("Hello world"); - * const file = await Deno.open("/foo/bar.txt"); + * const file = await Deno.open("/foo/bar.txt", { write: true }); * const bytesWritten = await Deno.write(file.rid, data); // 11 * Deno.close(file.rid); * ``` @@ -897,7 +897,11 @@ declare namespace Deno { export interface MakeTempOptions { /** Directory where the temporary directory should be created (defaults to - * the env variable TMPDIR, or the system's default, usually /tmp). */ + * the env variable TMPDIR, or the system's default, usually /tmp). + * + * Note that if the passed `dir` is relative, the path returned by + * makeTempFile() and makeTempDir() will also be relative. Be mindful of + * this when changing working directory. */ dir?: string; /** String that should precede the random portion of the temporary * directory's name. */ @@ -1253,7 +1257,9 @@ declare namespace Deno { * console.log(realSymLinkPath); // outputs "/home/alice/file.txt" * ``` * - * Requires `allow-read` permission. */ + * Requires `allow-read` permission for the target path. + * Also requires `allow-read` permission for the CWD if the target path is + * relative.*/ export function realPathSync(path: string): string; /** Resolves to the absolute normalized path, with symbolic links resolved. @@ -1267,7 +1273,9 @@ declare namespace Deno { * console.log(realSymLinkPath); // outputs "/home/alice/file.txt" * ``` * - * Requires `allow-read` permission. */ + * Requires `allow-read` permission for the target path. + * Also requires `allow-read` permission for the CWD if the target path is + * relative.*/ export function realPath(path: string): Promise; export interface DirEntry { @@ -1358,6 +1366,7 @@ declare namespace Deno { * points to. * * ```ts + * import { assert } from "https://deno.land/std/testing/asserts.ts"; * const fileInfo = await Deno.lstat("hello.txt"); * assert(fileInfo.isFile); * ``` @@ -1381,6 +1390,7 @@ declare namespace Deno { * follow symlinks. * * ```ts + * import { assert } from "https://deno.land/std/testing/asserts.ts"; * const fileInfo = await Deno.stat("hello.txt"); * assert(fileInfo.isFile); * ``` @@ -1392,6 +1402,7 @@ declare namespace Deno { * always follow symlinks. * * ```ts + * import { assert } from "https://deno.land/std/testing/asserts.ts"; * const fileInfo = Deno.statSync("hello.txt"); * assert(fileInfo.isFile); * ``` diff --git a/cli/js/lib.deno.shared_globals.d.ts b/cli/js/lib.deno.shared_globals.d.ts index f3a565668654e2..a93ff1166cdda1 100644 --- a/cli/js/lib.deno.shared_globals.d.ts +++ b/cli/js/lib.deno.shared_globals.d.ts @@ -174,34 +174,92 @@ declare namespace WebAssembly { } } -/** Sets a timer which executes a function once after the timer expires. */ +/** Sets a timer which executes a function once after the timer expires. Returns + * an id which may be used to cancel the timeout. + * + * setTimeout(() => { console.log('hello'); }, 500); + */ declare function setTimeout( + /** callback function to execute when timer expires */ cb: (...args: any[]) => void, + /** delay in ms */ delay?: number, + /** arguments passed to callback function */ ...args: any[] ): number; -/** Repeatedly calls a function , with a fixed time delay between each call. */ +/** Repeatedly calls a function , with a fixed time delay between each call. + * + * // Outputs 'hello' to the console every 500ms + * setInterval(() => { console.log('hello'); }, 500); + */ declare function setInterval( + /** callback function to execute when timer expires */ cb: (...args: any[]) => void, + /** delay in ms */ delay?: number, + /** arguments passed to callback function */ ...args: any[] ): number; -declare function clearTimeout(id?: number): void; + +/** Cancels a timed, repeating action which was previously started by a call + * to `setInterval()` + * + * const id = setInterval(()= > {console.log('hello');}, 500); + * ... + * clearInterval(id); + */ declare function clearInterval(id?: number): void; + +/** Cancels a scheduled action initiated by `setTimeout()` + * + * const id = setTimeout(()= > {console.log('hello');}, 500); + * ... + * clearTimeout(id); + */ +declare function clearTimeout(id?: number): void; + +/** A microtask is a short function which is executed after the function or + * module which created it exits and only if the JavaScript execution stack is + * empty, but before returning control to the event loop being used to drive the + * script's execution environment. This event loop may be either the main event + * loop or the event loop driving a web worker. + * + * queueMicrotask(() => { console.log('This event loop stack is complete'); }); + */ declare function queueMicrotask(func: Function): void; declare var console: Console; declare var crypto: Crypto; +/** Registers an event listener in the global scope, which will be called + * synchronously whenever the event `type` is dispatched. + * + * addEventListener('unload', () => { console.log('All finished!'); }); + * ... + * dispatchEvent(new Event('unload')); + */ declare function addEventListener( type: string, callback: EventListenerOrEventListenerObject | null, options?: boolean | AddEventListenerOptions | undefined ): void; +/** Dispatches an event in the global scope, synchronously invoking any + * registered event listeners for this event in the appropriate order. Returns + * false if event is cancelable and at least one of the event handlers which + * handled this event called Event.preventDefault(). Otherwise it returns true. + * + * dispatchEvent(new Event('unload')); + */ declare function dispatchEvent(event: Event): boolean; +/** Remove a previously registered event listener from the global scope + * + * const lstnr = () => { console.log('hello'); }; + * addEventListener('load', lstnr); + * removeEventListener('load', lstnr); + */ declare function removeEventListener( type: string, callback: EventListenerOrEventListenerObject | null, @@ -956,15 +1014,29 @@ declare const Response: { redirect(url: string, status?: number): Response; }; -/** Fetch a resource from the network. */ +/** Fetch a resource from the network. It returns a Promise that resolves to the + * Response to that request, whether it is successful or not. + * + * const response = await fetch("http://my.json.host/data.json"); + * console.log(response.status); // e.g. 200 + * console.log(response.statusText); // e.g. "OK" + * const jsonData = await response.json(); + */ declare function fetch( input: Request | URL | string, init?: RequestInit ): Promise; +/** Decodes a string of data which has been encoded using base-64 encoding. + * + * console.log(atob("aGVsbG8gd29ybGQ=")); // outputs 'hello world' + */ declare function atob(s: string): string; -/** Creates a base-64 ASCII string from the input string. */ +/** Creates a base-64 ASCII encoded string from the input string. + * + * console.log(btoa("hello world")); // outputs "aGVsbG8gd29ybGQ=" + */ declare function btoa(s: string): string; declare class TextDecoder { @@ -1266,7 +1338,7 @@ declare class Worker extends EventTarget { declare namespace performance { /** Returns a current time from Deno's start in milliseconds. * - * Use the flag --allow-hrtime return a precise value. + * Use the permission flag `--allow-hrtime` return a precise value. * * ```ts * const t = performance.now(); @@ -1481,3 +1553,11 @@ declare const AbortSignal: { prototype: AbortSignal; new (): AbortSignal; }; + +interface ErrorConstructor { + /** See https://v8.dev/docs/stack-trace-api#stack-trace-collection-for-custom-exceptions. */ + // eslint-disable-next-line @typescript-eslint/ban-types + captureStackTrace(error: Object, constructor?: Function): void; + // TODO(nayeemrmn): Support `Error.prepareStackTrace()`. We currently use this + // internally in a way that makes it unavailable for users. +} diff --git a/cli/js/lib.deno.unstable.d.ts b/cli/js/lib.deno.unstable.d.ts index 86bdd7b126c304..aebd7f964142fb 100644 --- a/cli/js/lib.deno.unstable.d.ts +++ b/cli/js/lib.deno.unstable.d.ts @@ -21,7 +21,9 @@ declare namespace Deno { */ export function umask(mask?: number): number; - /** Synchronously creates `newpath` as a hard link to `oldpath`. + /** **UNSTABLE**: This API needs a security review. + * + * Synchronously creates `newpath` as a hard link to `oldpath`. * * ```ts * Deno.linkSync("old/name", "new/name"); @@ -30,9 +32,9 @@ declare namespace Deno { * Requires `allow-read` and `allow-write` permissions. */ export function linkSync(oldpath: string, newpath: string): void; - /** Creates `newpath` as a hard link to `oldpath`. + /** **UNSTABLE**: This API needs a security review. * - * **UNSTABLE**: needs security review. + * Creates `newpath` as a hard link to `oldpath`. * * ```ts * await Deno.link("old/name", "new/name"); @@ -45,7 +47,7 @@ declare namespace Deno { type: "file" | "dir"; }; - /** **UNSTABLE**: needs security review. + /** **UNSTABLE**: This API needs a security review. * * Creates `newpath` as a symbolic link to `oldpath`. * @@ -63,7 +65,7 @@ declare namespace Deno { options?: SymlinkOptions ): void; - /** **UNSTABLE**: needs security review. + /** **UNSTABLE**: This API needs a security review. * * Creates `newpath` as a symbolic link to `oldpath`. * @@ -250,7 +252,10 @@ declare namespace Deno { */ export function dir(kind: DirKind): string | null; - /** Returns an array containing the 1, 5, and 15 minute load averages. The + /** **Unstable** There are questions around which permission this needs. And + * maybe should be renamed (loadAverage?) + * + * Returns an array containing the 1, 5, and 15 minute load averages. The * load average is a measure of CPU and IO utilization of the last one, five, * and 15 minute periods expressed as a fractional number. Zero means there * is no load. On Windows, the three values are always the same and represent @@ -261,13 +266,14 @@ declare namespace Deno { * ``` * * Requires `allow-env` permission. - * - * **Unstable** There are questions around which permission this needs. And - * maybe should be renamed (loadAverage?) */ export function loadavg(): number[]; - /** Returns the release version of the Operating System. + /** **Unstable** new API. yet to be vetted. Under consideration to possibly move to + * Deno.build or Deno.versions and if it should depend sys-info, which may not + * be desireable. + * + * Returns the release version of the Operating System. * * ```ts * console.log(Deno.osRelease()); @@ -275,8 +281,6 @@ declare namespace Deno { * * Requires `allow-env` permission. * - * **Unstable** new API maybe move to Deno.build or Deno.versions? Depends on - * sys-info, which we don't necessarally want to depend on. */ export function osRelease(): string; @@ -699,7 +703,7 @@ declare namespace Deno { columnNumber: number; } - /** UNSTABLE: new API, yet to be vetted. + /** **UNSTABLE**: new API, yet to be vetted. * * Given a current location in a module, lookup the source location and return * it. @@ -793,7 +797,7 @@ declare namespace Deno { SIGUSR2 = 31, } - /** **UNSTABLE**: make platform independent. + /** **UNSTABLE**: Further changes required to make platform independent. * * Signals numbers. This is platform dependent. */ export const Signal: typeof MacOSSignal | typeof LinuxSignal; @@ -947,7 +951,7 @@ declare namespace Deno { mtime: number | Date ): Promise; - /** **UNSTABLE**: Maybe remove `ShutdownMode` entirely. + /** **UNSTABLE**: Under consideration to remove `ShutdownMode` entirely. * * Corresponds to `SHUT_RD`, `SHUT_WR`, `SHUT_RDWR` on POSIX-like systems. * @@ -973,7 +977,7 @@ declare namespace Deno { */ export function shutdown(rid: number, how: ShutdownMode): Promise; - /** **UNSTABLE**:: new API, yet to be vetted. + /** **UNSTABLE**: new API, yet to be vetted. * * A generic transport listener for message-oriented protocols. */ export interface DatagramConn extends AsyncIterable<[Uint8Array, Addr]> { @@ -1013,7 +1017,7 @@ declare namespace Deno { options: UnixListenOptions & { transport: "unix" } ): Listener; - /** **UNSTABLE**: new API + /** **UNSTABLE**: new API, yet to be vetted * * Listen announces on the local transport address. * @@ -1034,7 +1038,7 @@ declare namespace Deno { options: ListenOptions & { transport: "udp" } ): DatagramConn; - /** **UNSTABLE**: new API + /** **UNSTABLE**: new API, yet to be vetted * * Listen announces on the local transport address. * @@ -1055,7 +1059,9 @@ declare namespace Deno { path: string; } - /** + /** **UNSTABLE**: The unix socket transport is unstable as a new API yet to + * be vetted. The TCP transport is considered stable. + * * Connects to the hostname (default is "127.0.0.1") and port on the named * transport (default is "tcp"), and resolves to the connection (`Conn`). * @@ -1216,8 +1222,8 @@ declare namespace Deno { request(desc: PermissionDescriptor): Promise; } - /** **UNSTABLE**: maybe move to `navigator.permissions` to match web API. It - * could look like `navigator.permissions.query({ name: Deno.symbols.read })`. + /** **UNSTABLE**: Under consideration to move to `navigator.permissions` to + * match web API. It could look like `navigator.permissions.query({ name: Deno.symbols.read })`. */ export const permissions: Permissions; @@ -1227,7 +1233,10 @@ declare namespace Deno { constructor(state: PermissionState); } - /** Get the `hostname` of the machine the Deno process is running on. + /** **UNSTABLE**: New API, yet to be vetted. Additional consideration is still + * necessary around the permissions required. + * + * Get the `hostname` of the machine the Deno process is running on. * * ```ts * console.log(Deno.hostname()); diff --git a/cli/js/ops/dispatch_json.ts b/cli/js/ops/dispatch_json.ts index 9ff0f13f5980e4..6292c188a339ac 100644 --- a/cli/js/ops/dispatch_json.ts +++ b/cli/js/ops/dispatch_json.ts @@ -59,12 +59,12 @@ export function asyncMsgFromRust(resUi8: Uint8Array): void { export function sendSync( opName: string, args: object = {}, - zeroCopy?: Uint8Array + ...zeroCopy: Uint8Array[] ): Ok { const opId = OPS_CACHE[opName]; util.log("sendSync", opName, opId); const argsUi8 = encode(args); - const resUi8 = core.dispatch(opId, argsUi8, zeroCopy); + const resUi8 = core.dispatch(opId, argsUi8, ...zeroCopy); util.assert(resUi8 != null); const res = decode(resUi8); @@ -75,7 +75,7 @@ export function sendSync( export async function sendAsync( opName: string, args: object = {}, - zeroCopy?: Uint8Array + ...zeroCopy: Uint8Array[] ): Promise { const opId = OPS_CACHE[opName]; util.log("sendAsync", opName, opId); @@ -84,7 +84,7 @@ export async function sendAsync( const promise = util.createResolvable(); const argsUi8 = encode(args); - const buf = core.dispatch(opId, argsUi8, zeroCopy); + const buf = core.dispatch(opId, argsUi8, ...zeroCopy); if (buf) { // Sync result. const res = decode(buf); diff --git a/cli/js/ops/fetch.ts b/cli/js/ops/fetch.ts index 09b9ac1eca3e0a..290376c8604a07 100644 --- a/cli/js/ops/fetch.ts +++ b/cli/js/ops/fetch.ts @@ -24,5 +24,5 @@ export function fetch( zeroCopy = new Uint8Array(body.buffer, body.byteOffset, body.byteLength); } - return sendAsync("op_fetch", args, zeroCopy); + return sendAsync("op_fetch", args, ...(zeroCopy ? [zeroCopy] : [])); } diff --git a/cli/js/repl.ts b/cli/js/repl.ts index 79273ed33844e5..d1cf63cd918a85 100644 --- a/cli/js/repl.ts +++ b/cli/js/repl.ts @@ -35,8 +35,8 @@ function isRecoverableError(e: Error): boolean { // Returns `true` if `close()` is called in REPL. // We should quit the REPL when this function returns `true`. function isCloseCalled(): boolean { - // @ts-expect-error - return globalThis.closed; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return (globalThis as any).closed; } // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -49,11 +49,18 @@ let lastThrownError: Value = undefined; // Returns true if code is consumed (no error/irrecoverable error). // Returns false if error is recoverable function evaluate(code: string): boolean { - const [result, errInfo] = core.evalContext(code); + // each evalContext is a separate function body, and we want strict mode to + // work, so we should ensure that the code starts with "use strict" + const [result, errInfo] = core.evalContext(`"use strict";\n\n${code}`); if (!errInfo) { - lastEvalResult = result; + // when a function is eval'ed with just "use strict" sometimes the result + // is "use strict" which should be discarded + lastEvalResult = + typeof result === "string" && result === "use strict" + ? undefined + : result; if (!isCloseCalled()) { - replLog(result); + replLog(lastEvalResult); } } else if (errInfo.isCompileError && isRecoverableError(errInfo.thrown)) { // Recoverable compiler error diff --git a/cli/js/runtime_main.ts b/cli/js/runtime_main.ts index 97205d205fa5ce..58405f673a1111 100644 --- a/cli/js/runtime_main.ts +++ b/cli/js/runtime_main.ts @@ -30,8 +30,8 @@ import { log, immutableDefine } from "./util.ts"; // TODO: factor out `Deno` global assignment to separate function // Add internal object to Deno object. // This is not exposed as part of the Deno types. -// @ts-expect-error -denoNs[internalSymbol] = internalObject; +// eslint-disable-next-line @typescript-eslint/no-explicit-any +(denoNs as any)[internalSymbol] = internalObject; let windowIsClosing = false; @@ -71,8 +71,8 @@ export function bootstrapMainRuntime(): void { throw new Error("Worker runtime already bootstrapped"); } // Remove bootstrapping methods from global scope - // @ts-expect-error - globalThis.bootstrap = undefined; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (globalThis as any).bootstrap = undefined; log("bootstrapMainRuntime"); hasBootstrapped = true; Object.defineProperties(globalThis, windowOrWorkerGlobalScopeMethods); diff --git a/cli/js/runtime_worker.ts b/cli/js/runtime_worker.ts index 4e92663a1bef07..3f7816990dc03a 100644 --- a/cli/js/runtime_worker.ts +++ b/cli/js/runtime_worker.ts @@ -33,8 +33,8 @@ import { setSignals } from "./signals.ts"; // TODO: factor out `Deno` global assignment to separate function // Add internal object to Deno object. // This is not exposed as part of the Deno types. -// @ts-expect-error -denoNs[internalSymbol] = internalObject; +// eslint-disable-next-line @typescript-eslint/no-explicit-any +(denoNs as any)[internalSymbol] = internalObject; const encoder = new TextEncoder(); @@ -128,8 +128,8 @@ export function bootstrapWorkerRuntime( throw new Error("Worker runtime already bootstrapped"); } // Remove bootstrapping methods from global scope - // @ts-expect-error - globalThis.bootstrap = undefined; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (globalThis as any).bootstrap = undefined; log("bootstrapWorkerRuntime"); hasBootstrapped = true; Object.defineProperties(globalThis, windowOrWorkerGlobalScopeMethods); diff --git a/cli/js/testing.ts b/cli/js/testing.ts index fc32fd604f7216..ffe978888da780 100644 --- a/cli/js/testing.ts +++ b/cli/js/testing.ts @@ -336,8 +336,8 @@ async function runTests({ const originalConsole = globalThis.console; if (disableLog) { - // @ts-expect-error - globalThis.console = disabledConsole; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (globalThis as any).console = disabledConsole; } let endMsg: TestMessage["end"]; diff --git a/cli/js/web/blob.ts b/cli/js/web/blob.ts index 546ea7e8200382..899c671358ea74 100644 --- a/cli/js/web/blob.ts +++ b/cli/js/web/blob.ts @@ -161,7 +161,7 @@ async function readBytes( // Ensures it does not impact garbage collection. export const blobBytesWeakMap = new WeakMap(); -export class DenoBlob implements Blob { +class DenoBlob implements Blob { [bytesSymbol]: Uint8Array; readonly size: number = 0; readonly type: string = ""; @@ -216,3 +216,11 @@ export class DenoBlob implements Blob { return readBytes(getStream(this[bytesSymbol]).getReader()); } } + +// we want the Base class name to be the name of the class. +Object.defineProperty(DenoBlob, "name", { + value: "Blob", + configurable: true, +}); + +export { DenoBlob }; diff --git a/cli/js/web/body.ts b/cli/js/web/body.ts index ffe3f0e595aedc..672b7de5ac339b 100644 --- a/cli/js/web/body.ts +++ b/cli/js/web/body.ts @@ -2,7 +2,9 @@ import * as blob from "./blob.ts"; import * as encoding from "./text_encoding.ts"; import * as domTypes from "./dom_types.d.ts"; import { ReadableStreamImpl } from "./streams/readable_stream.ts"; +import { isReadableStreamDisturbed } from "./streams/internals.ts"; import { getHeaderValueParams, hasHeaderValueOf } from "./util.ts"; +import { MultipartParser } from "./fetch/multipart.ts"; // only namespace imports work for now, plucking out what we need const { TextEncoder, TextDecoder } = encoding; @@ -115,7 +117,7 @@ export class Body implements domTypes.Body { } get bodyUsed(): boolean { - if (this.body && this.body.locked) { + if (this.body && isReadableStreamDisturbed(this.body)) { return true; } return false; @@ -130,98 +132,15 @@ export class Body implements domTypes.Body { // ref: https://fetch.spec.whatwg.org/#body-mixin public async formData(): Promise { const formData = new FormData(); - const enc = new TextEncoder(); if (hasHeaderValueOf(this.contentType, "multipart/form-data")) { const params = getHeaderValueParams(this.contentType); - if (!params.has("boundary")) { - // TypeError is required by spec - throw new TypeError("multipart/form-data must provide a boundary"); - } + // ref: https://tools.ietf.org/html/rfc2046#section-5.1 const boundary = params.get("boundary")!; - const dashBoundary = `--${boundary}`; - const delimiter = `\r\n${dashBoundary}`; - const closeDelimiter = `${delimiter}--`; - - const body = await this.text(); - let bodyParts: string[]; - const bodyEpilogueSplit = body.split(closeDelimiter); - if (bodyEpilogueSplit.length < 2) { - bodyParts = []; - } else { - // discard epilogue - const bodyEpilogueTrimmed = bodyEpilogueSplit[0]; - // first boundary treated special due to optional prefixed \r\n - const firstBoundaryIndex = bodyEpilogueTrimmed.indexOf(dashBoundary); - if (firstBoundaryIndex < 0) { - throw new TypeError("Invalid boundary"); - } - const bodyPreambleTrimmed = bodyEpilogueTrimmed - .slice(firstBoundaryIndex + dashBoundary.length) - .replace(/^[\s\r\n\t]+/, ""); // remove transport-padding CRLF - // trimStart might not be available - // Be careful! body-part allows trailing \r\n! - // (as long as it is not part of `delimiter`) - bodyParts = bodyPreambleTrimmed - .split(delimiter) - .map((s): string => s.replace(/^[\s\r\n\t]+/, "")); - // TODO: LWSP definition is actually trickier, - // but should be fine in our case since without headers - // we should just discard the part - } - for (const bodyPart of bodyParts) { - const headers = new Headers(); - const headerOctetSeperatorIndex = bodyPart.indexOf("\r\n\r\n"); - if (headerOctetSeperatorIndex < 0) { - continue; // Skip unknown part - } - const headerText = bodyPart.slice(0, headerOctetSeperatorIndex); - const octets = bodyPart.slice(headerOctetSeperatorIndex + 4); + const body = new Uint8Array(await this.arrayBuffer()); + const multipartParser = new MultipartParser(body, boundary); - // TODO: use textproto.readMIMEHeader from deno_std - const rawHeaders = headerText.split("\r\n"); - for (const rawHeader of rawHeaders) { - const sepIndex = rawHeader.indexOf(":"); - if (sepIndex < 0) { - continue; // Skip this header - } - const key = rawHeader.slice(0, sepIndex); - const value = rawHeader.slice(sepIndex + 1); - headers.set(key, value); - } - if (!headers.has("content-disposition")) { - continue; // Skip unknown part - } - // Content-Transfer-Encoding Deprecated - const contentDisposition = headers.get("content-disposition")!; - const partContentType = headers.get("content-type") || "text/plain"; - // TODO: custom charset encoding (needs TextEncoder support) - // const contentTypeCharset = - // getHeaderValueParams(partContentType).get("charset") || ""; - if (!hasHeaderValueOf(contentDisposition, "form-data")) { - continue; // Skip, might not be form-data - } - const dispositionParams = getHeaderValueParams(contentDisposition); - if (!dispositionParams.has("name")) { - continue; // Skip, unknown name - } - const dispositionName = dispositionParams.get("name")!; - if (dispositionParams.has("filename")) { - const filename = dispositionParams.get("filename")!; - const blob = new DenoBlob([enc.encode(octets)], { - type: partContentType, - }); - // TODO: based on spec - // https://xhr.spec.whatwg.org/#dom-formdata-append - // https://xhr.spec.whatwg.org/#create-an-entry - // Currently it does not mention how I could pass content-type - // to the internally created file object... - formData.append(dispositionName, blob, filename); - } else { - formData.append(dispositionName, octets); - } - } - return formData; + return multipartParser.parse(); } else if ( hasHeaderValueOf(this.contentType, "application/x-www-form-urlencoded") ) { diff --git a/cli/js/web/console.ts b/cli/js/web/console.ts index 69c9d3137a1c19..9f166b373f6bf3 100644 --- a/cli/js/web/console.ts +++ b/cli/js/web/console.ts @@ -124,23 +124,19 @@ function createIterableString( const iPrefix = `${config.displayName ? config.displayName + " " : ""}`; + const initIndentation = `\n${DEFAULT_INDENT.repeat(level + 1)}`; + const entryIndentation = `,\n${DEFAULT_INDENT.repeat(level + 1)}`; + const closingIndentation = `\n${DEFAULT_INDENT.repeat(level)}`; + let iContent: string; if (config.group && entries.length > MIN_GROUP_LENGTH) { const groups = groupEntries(entries, level, value); - const initIndentation = `\n${DEFAULT_INDENT.repeat(level + 1)}`; - const entryIndentation = `,\n${DEFAULT_INDENT.repeat(level + 1)}`; - const closingIndentation = `\n${DEFAULT_INDENT.repeat(level)}`; - iContent = `${initIndentation}${groups.join( entryIndentation )}${closingIndentation}`; } else { iContent = entries.length === 0 ? "" : ` ${entries.join(", ")} `; if (stripColor(iContent).length > LINE_BREAKING_LENGTH) { - const initIndentation = `\n${DEFAULT_INDENT.repeat(level + 1)}`; - const entryIndentation = `,\n${DEFAULT_INDENT.repeat(level + 1)}`; - const closingIndentation = `\n`; - iContent = `${initIndentation}${entries.join( entryIndentation )}${closingIndentation}`; @@ -220,14 +216,18 @@ function groupEntries( lineMaxLength += separatorSpace; maxLineLength[i] = lineMaxLength; } - let order = "padStart"; + let order: "padStart" | "padEnd" = "padStart"; if (value !== undefined) { for (let i = 0; i < entries.length; i++) { - //@ts-expect-error - if (typeof value[i] !== "number" && typeof value[i] !== "bigint") { + /* eslint-disable @typescript-eslint/no-explicit-any */ + if ( + typeof (value as any)[i] !== "number" && + typeof (value as any)[i] !== "bigint" + ) { order = "padEnd"; break; } + /* eslint-enable */ } } // Each iteration creates a single line of grouped entries. @@ -239,7 +239,6 @@ function groupEntries( for (; j < max - 1; j++) { // In future, colors should be taken here into the account const padding = maxLineLength[j - i]; - //@ts-expect-error str += `${entries[j]}, `[order](padding, " "); } if (order === "padStart") { @@ -343,7 +342,7 @@ function createArrayString( const ending = emptyItems > 1 ? "s" : ""; return dim(`<${emptyItems} empty item${ending}>`); } else { - return stringifyWithQuotes(val, ctx, level + 1, maxLevel); + return stringifyWithQuotes(val, ctx, level, maxLevel); } }, group: true, @@ -412,8 +411,8 @@ function createMapString( }, group: false, }; - //@ts-expect-error - return createIterableString(value, ctx, level, maxLevel, printConfig); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return createIterableString(value as any, ctx, level, maxLevel, printConfig); } function createWeakSetString(): string { @@ -481,7 +480,7 @@ function createPromiseString( // TODO: Proxy function createRawObjectString( - value: { [key: string]: unknown }, + value: Record, ctx: ConsoleContext, level: number, maxLevel: number @@ -494,8 +493,9 @@ function createRawObjectString( let baseString = ""; let shouldShowDisplayName = false; - // @ts-expect-error - let displayName = value[Symbol.toStringTag]; + let displayName = (value as { [Symbol.toStringTag]: string })[ + Symbol.toStringTag + ]; if (!displayName) { displayName = getClassInstanceName(value); } @@ -515,8 +515,8 @@ function createRawObjectString( for (const key of symbolKeys) { entries.push( `${key.toString()}: ${stringifyWithQuotes( - // @ts-expect-error - value[key], + // eslint-disable-next-line @typescript-eslint/no-explicit-any + value[key as any], ctx, level + 1, maxLevel @@ -842,27 +842,33 @@ export class Console { resultData = data!; } + let hasPrimitives = false; Object.keys(resultData).forEach((k, idx): void => { const value: unknown = resultData[k]!; - - if (value !== null && typeof value === "object") { - Object.entries(value as { [key: string]: unknown }).forEach( - ([k, v]): void => { - if (properties && !properties.includes(k)) { - return; + const primitive = + value === null || + (typeof value !== "function" && typeof value !== "object"); + if (properties === undefined && primitive) { + hasPrimitives = true; + values.push(stringifyValue(value)); + } else { + const valueObj = (value as { [key: string]: unknown }) || {}; + const keys = properties || Object.keys(valueObj); + for (const k of keys) { + if (primitive || !valueObj.hasOwnProperty(k)) { + if (objectValues[k]) { + // fill with blanks for idx to avoid misplacing from later values + objectValues[k].push(""); } - + } else { if (objectValues[k]) { - objectValues[k].push(stringifyValue(v)); + objectValues[k].push(stringifyValue(valueObj[k])); } else { - objectValues[k] = createColumn(v, idx); + objectValues[k] = createColumn(valueObj[k], idx); } } - ); - + } values.push(""); - } else { - values.push(stringifyValue(value)); } indexKeys.push(k); @@ -872,10 +878,7 @@ export class Console { const bodyValues = Object.values(objectValues); const header = [ indexKey, - ...(properties || [ - ...headerKeys, - !isMap && values.length > 0 && valuesKey, - ]), + ...(properties || [...headerKeys, !isMap && hasPrimitives && valuesKey]), ].filter(Boolean) as string[]; const body = [indexKeys, ...bodyValues, values]; @@ -949,7 +952,6 @@ export class Console { name: "Trace", message, }; - // @ts-expect-error Error.captureStackTrace(err, this.trace); this.error((err as Error).stack); }; diff --git a/cli/js/web/fetch.ts b/cli/js/web/fetch.ts index a56ebe772e0491..47ed1a7d17a93d 100644 --- a/cli/js/web/fetch.ts +++ b/cli/js/web/fetch.ts @@ -12,6 +12,9 @@ import { DomFileImpl } from "./dom_file.ts"; import { getHeaderValueParams } from "./util.ts"; import { ReadableStreamImpl } from "./streams/readable_stream.ts"; +const NULL_BODY_STATUS = [101, 204, 205, 304]; +const REDIRECT_STATUS = [301, 302, 303, 307, 308]; + const responseData = new WeakMap(); export class Response extends Body.Body implements domTypes.Response { readonly type: ResponseType; @@ -45,7 +48,7 @@ export class Response extends Body.Body implements domTypes.Response { } // null body status - if (body && [/* 101, */ 204, 205, 304].includes(status)) { + if (body && NULL_BODY_STATUS.includes(status)) { throw new TypeError("Response with null body status cannot have body"); } @@ -112,7 +115,7 @@ export class Response extends Body.Body implements domTypes.Response { this.url = url; this.statusText = statusText; - this.status = status; + this.status = extraInit.status || status; this.headers = headers; this.redirected = extraInit.redirected; this.type = type; @@ -184,7 +187,7 @@ function sendFetchReq( } export async function fetch( - input: domTypes.Request | URL | string, + input: (domTypes.Request & { _bodySource?: unknown }) | URL | string, init?: domTypes.RequestInit ): Promise { let url: string; @@ -282,40 +285,51 @@ export async function fetch( method = input.method; headers = input.headers; - //@ts-expect-error if (input._bodySource) { body = new DataView(await input.arrayBuffer()); } } + let responseBody; + let responseInit: ResponseInit = {}; while (remRedirectCount) { const fetchResponse = await sendFetchReq(url, method, headers, body); - const responseBody = new ReadableStreamImpl({ - async pull(controller: ReadableStreamDefaultController): Promise { - try { - const b = new Uint8Array(1024 * 32); - const result = await read(fetchResponse.bodyRid, b); - if (result === null) { + if ( + NULL_BODY_STATUS.includes(fetchResponse.status) || + REDIRECT_STATUS.includes(fetchResponse.status) + ) { + // We won't use body of received response, so close it now + // otherwise it will be kept in resource table. + close(fetchResponse.bodyRid); + responseBody = null; + } else { + responseBody = new ReadableStreamImpl({ + async pull(controller: ReadableStreamDefaultController): Promise { + try { + const b = new Uint8Array(1024 * 32); + const result = await read(fetchResponse.bodyRid, b); + if (result === null) { + controller.close(); + return close(fetchResponse.bodyRid); + } + + controller.enqueue(b.subarray(0, result)); + } catch (e) { + controller.error(e); controller.close(); - return close(fetchResponse.bodyRid); + close(fetchResponse.bodyRid); } - - controller.enqueue(b.subarray(0, result)); - } catch (e) { - controller.error(e); - controller.close(); + }, + cancel(): void { + // When reader.cancel() is called close(fetchResponse.bodyRid); - } - }, - cancel(): void { - // When reader.cancel() is called - close(fetchResponse.bodyRid); - }, - }); + }, + }); + } - let responseInit: ResponseInit = { - status: fetchResponse.status, + responseInit = { + status: 200, statusText: fetchResponse.statusText, headers: fetchResponse.headers, }; @@ -323,15 +337,13 @@ export async function fetch( responseData.set(responseInit, { redirected, rid: fetchResponse.bodyRid, + status: fetchResponse.status, url, }); const response = new Response(responseBody, responseInit); - if ([301, 302, 303, 307, 308].includes(fetchResponse.status)) { - // We won't use body of received response, so close it now - // otherwise it will be kept in resource table. - close(fetchResponse.bodyRid); + if (REDIRECT_STATUS.includes(fetchResponse.status)) { // We're in a redirect status switch ((init && init.redirect) || "follow") { case "error": @@ -374,6 +386,12 @@ export async function fetch( return response; } } - // Return a network error due to too many redirections - throw notImplemented(); + + responseData.set(responseInit, { + type: "error", + redirected: false, + url: "", + }); + + return new Response(null, responseInit); } diff --git a/cli/js/web/fetch/multipart.ts b/cli/js/web/fetch/multipart.ts new file mode 100644 index 00000000000000..792f9b5ee11a6b --- /dev/null +++ b/cli/js/web/fetch/multipart.ts @@ -0,0 +1,120 @@ +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. + +import { DenoBlob } from "../blob.ts"; +import { TextEncoder, TextDecoder } from "../text_encoding.ts"; +import { getHeaderValueParams } from "../util.ts"; + +const decoder = new TextDecoder(); +const encoder = new TextEncoder(); +const CR = "\r".charCodeAt(0); +const LF = "\n".charCodeAt(0); + +interface MultipartHeaders { + headers: Headers; + disposition: Map; +} + +export class MultipartParser { + readonly boundary: string; + readonly boundaryChars: Uint8Array; + readonly body: Uint8Array; + constructor(body: Uint8Array, boundary: string) { + if (!boundary) { + throw new TypeError("multipart/form-data must provide a boundary"); + } + + this.boundary = `--${boundary}`; + this.body = body; + this.boundaryChars = encoder.encode(this.boundary); + } + + #parseHeaders = (headersText: string): MultipartHeaders => { + const headers = new Headers(); + const rawHeaders = headersText.split("\r\n"); + for (const rawHeader of rawHeaders) { + const sepIndex = rawHeader.indexOf(":"); + if (sepIndex < 0) { + continue; // Skip this header + } + const key = rawHeader.slice(0, sepIndex); + const value = rawHeader.slice(sepIndex + 1); + headers.set(key, value); + } + + return { + headers, + disposition: getHeaderValueParams( + headers.get("Content-Disposition") ?? "" + ), + }; + }; + + parse(): FormData { + const formData = new FormData(); + let headerText = ""; + let boundaryIndex = 0; + let state = 0; + let fileStart = 0; + + for (let i = 0; i < this.body.length; i++) { + const byte = this.body[i]; + const prevByte = this.body[i - 1]; + const isNewLine = byte === LF && prevByte === CR; + + if (state === 1 || state === 2 || state == 3) { + headerText += String.fromCharCode(byte); + } + if (state === 0 && isNewLine) { + state = 1; + } else if (state === 1 && isNewLine) { + state = 2; + const headersDone = this.body[i + 1] === CR && this.body[i + 2] === LF; + + if (headersDone) { + state = 3; + } + } else if (state === 2 && isNewLine) { + state = 3; + } else if (state === 3 && isNewLine) { + state = 4; + fileStart = i + 1; + } else if (state === 4) { + if (this.boundaryChars[boundaryIndex] !== byte) { + boundaryIndex = 0; + } else { + boundaryIndex++; + } + + if (boundaryIndex >= this.boundary.length) { + const { headers, disposition } = this.#parseHeaders(headerText); + const content = this.body.subarray(fileStart, i - boundaryIndex - 1); + // https://fetch.spec.whatwg.org/#ref-for-dom-body-formdata + const filename = disposition.get("filename"); + const name = disposition.get("name"); + + state = 5; + // Reset + boundaryIndex = 0; + headerText = ""; + + if (!name) { + continue; // Skip, unknown name + } + + if (filename) { + const blob = new DenoBlob([content], { + type: headers.get("Content-Type") || "application/octet-stream", + }); + formData.append(name, blob, filename); + } else { + formData.append(name, decoder.decode(content)); + } + } + } else if (state === 5 && isNewLine) { + state = 1; + } + } + + return formData; + } +} diff --git a/cli/js/web/form_data.ts b/cli/js/web/form_data.ts index 5fab02553f71d0..155f40771aef1b 100644 --- a/cli/js/web/form_data.ts +++ b/cli/js/web/form_data.ts @@ -22,7 +22,7 @@ class FormDataBase { if (value instanceof domFile.DomFileImpl) { this[dataSymbol].push([name, value]); } else if (value instanceof blob.DenoBlob) { - const dfile = new domFile.DomFileImpl([value], filename || name, { + const dfile = new domFile.DomFileImpl([value], filename || "blob", { type: value.type, }); this[dataSymbol].push([name, dfile]); @@ -96,7 +96,7 @@ class FormDataBase { if (value instanceof domFile.DomFileImpl) { this[dataSymbol][i][1] = value; } else if (value instanceof blob.DenoBlob) { - const dfile = new domFile.DomFileImpl([value], filename || name, { + const dfile = new domFile.DomFileImpl([value], filename || "blob", { type: value.type, }); this[dataSymbol][i][1] = dfile; @@ -117,7 +117,7 @@ class FormDataBase { if (value instanceof domFile.DomFileImpl) { this[dataSymbol].push([name, value]); } else if (value instanceof blob.DenoBlob) { - const dfile = new domFile.DomFileImpl([value], filename || name, { + const dfile = new domFile.DomFileImpl([value], filename || "blob", { type: value.type, }); this[dataSymbol].push([name, dfile]); @@ -137,3 +137,8 @@ export class FormDataImpl extends DomIterableMixin< FormDataEntryValue, typeof FormDataBase >(FormDataBase, dataSymbol) {} + +Object.defineProperty(FormDataImpl, "name", { + value: "FormData", + configurable: true, +}); diff --git a/cli/js/web/headers.ts b/cli/js/web/headers.ts index d803f69f3026c8..9e0a70f0d4471b 100644 --- a/cli/js/web/headers.ts +++ b/cli/js/web/headers.ts @@ -256,3 +256,8 @@ export class HeadersImpl extends DomIterableMixin< string, typeof HeadersBase >(HeadersBase, headersData) {} + +Object.defineProperty(HeadersImpl, "name", { + value: "Headers", + configurable: true, +}); diff --git a/cli/js/web/streams/internals.ts b/cli/js/web/streams/internals.ts index ff8fc25feef6f9..d0d35a1c37e8e2 100644 --- a/cli/js/web/streams/internals.ts +++ b/cli/js/web/streams/internals.ts @@ -367,6 +367,11 @@ export function isReadableStreamLocked(stream: ReadableStreamImpl): boolean { return stream[sym.reader] ? true : false; } +export function isReadableStreamDisturbed(stream: ReadableStream): boolean { + assert(isReadableStream(stream)); + return stream[sym.disturbed] ? true : false; +} + export function isTransformStream( x: unknown ): x is TransformStreamImpl { diff --git a/cli/js/web/timers.ts b/cli/js/web/timers.ts index 87b23de0675fac..245d6a8c7153c1 100644 --- a/cli/js/web/timers.ts +++ b/cli/js/web/timers.ts @@ -234,23 +234,23 @@ function setTimer( } export function setTimeout( + this: unknown, cb: (...args: Args) => void, delay = 0, ...args: Args ): number { checkBigInt(delay); - // @ts-expect-error checkThis(this); return setTimer(cb, delay, args, false); } export function setInterval( + this: unknown, cb: (...args: Args) => void, delay = 0, ...args: Args ): number { checkBigInt(delay); - // @ts-expect-error checkThis(this); return setTimer(cb, delay, args, true); } diff --git a/cli/main.rs b/cli/main.rs index 68ba5e7763c5c3..6d9aacf37c5055 100644 --- a/cli/main.rs +++ b/cli/main.rs @@ -72,17 +72,14 @@ use crate::file_fetcher::SourceFile; use crate::file_fetcher::SourceFileFetcher; use crate::fs as deno_fs; use crate::global_state::GlobalState; -use crate::import_map::ImportMap; use crate::msg::MediaType; use crate::op_error::OpError; -use crate::ops::io::get_stdio; use crate::permissions::Permissions; -use crate::state::exit_unstable; -use crate::state::State; use crate::tsc::TargetLib; use crate::worker::MainWorker; use deno_core::v8_set_flags; use deno_core::ErrBox; +use deno_core::EsIsolate; use deno_core::ModuleSpecifier; use flags::DenoSubcommand; use flags::Flags; @@ -139,28 +136,17 @@ fn write_to_stdout_ignore_sigpipe(bytes: &[u8]) -> Result<(), std::io::Error> { } } -fn create_main_worker( - global_state: GlobalState, - main_module: ModuleSpecifier, -) -> Result { - let state = State::new(global_state, None, main_module, false)?; - - let mut worker = MainWorker::new( - "main".to_string(), - startup_data::deno_isolate_init(), - state, - ); - - { - let (stdin, stdout, stderr) = get_stdio(); - let mut t = worker.resource_table.borrow_mut(); - t.add("stdin", Box::new(stdin)); - t.add("stdout", Box::new(stdout)); - t.add("stderr", Box::new(stderr)); +fn write_lockfile(global_state: GlobalState) -> Result<(), std::io::Error> { + if global_state.flags.lock_write { + if let Some(ref lockfile) = global_state.lockfile { + let g = lockfile.lock().unwrap(); + g.write()?; + } else { + eprintln!("--lock flag must be specified when using --lock-write"); + std::process::exit(11); + } } - - worker.execute("bootstrap.mainRuntime()")?; - Ok(worker) + Ok(()) } fn print_cache_info(state: &GlobalState) { @@ -207,16 +193,21 @@ async fn print_file_info( ); let module_specifier_ = module_specifier.clone(); + global_state - .clone() - .fetch_compiled_module( - module_specifier_, + .prepare_module_load( + module_specifier_.clone(), None, TargetLib::Main, Permissions::allow_all(), false, + global_state.maybe_import_map.clone(), ) .await?; + global_state + .clone() + .fetch_compiled_module(module_specifier_, None) + .await?; if out.media_type == msg::MediaType::TypeScript || (out.media_type == msg::MediaType::JavaScript @@ -246,7 +237,10 @@ async fn print_file_info( ); } - if let Some(deps) = worker.isolate.modules.deps(&module_specifier) { + let es_state_rc = EsIsolate::state(&worker.isolate); + let es_state = es_state_rc.borrow(); + + if let Some(deps) = es_state.modules.deps(&module_specifier) { println!("{}{}", colors::bold("deps:\n".to_string()), deps.name); if let Some(ref depsdeps) = deps.deps { for d in depsdeps { @@ -294,7 +288,7 @@ async fn info_command( } let main_module = ModuleSpecifier::resolve_url_or_path(&file.unwrap())?; - let mut worker = create_main_worker(global_state, main_module.clone())?; + let mut worker = MainWorker::create(global_state, main_module.clone())?; worker.preload_module(&main_module).await?; print_file_info(&worker, main_module.clone()).await } @@ -312,7 +306,7 @@ async fn install_command( fetch_flags.reload = true; let global_state = GlobalState::new(fetch_flags)?; let main_module = ModuleSpecifier::resolve_url_or_path(&module_url)?; - let mut worker = create_main_worker(global_state, main_module.clone())?; + let mut worker = MainWorker::create(global_state, main_module.clone())?; worker.preload_module(&main_module).await?; installer::install(flags, &module_url, args, name, root, force) .map_err(ErrBox::from) @@ -323,22 +317,14 @@ async fn cache_command(flags: Flags, files: Vec) -> Result<(), ErrBox> { ModuleSpecifier::resolve_url_or_path("./__$deno$fetch.ts").unwrap(); let global_state = GlobalState::new(flags)?; let mut worker = - create_main_worker(global_state.clone(), main_module.clone())?; + MainWorker::create(global_state.clone(), main_module.clone())?; for file in files { let specifier = ModuleSpecifier::resolve_url_or_path(&file)?; worker.preload_module(&specifier).await.map(|_| ())?; } - if global_state.flags.lock_write { - if let Some(ref lockfile) = global_state.lockfile { - let g = lockfile.lock().unwrap(); - g.write()?; - } else { - eprintln!("--lock flag must be specified when using --lock-write"); - std::process::exit(11); - } - } + write_lockfile(global_state)?; Ok(()) } @@ -352,7 +338,7 @@ async fn eval_command( let main_module = ModuleSpecifier::resolve_url_or_path("./__$deno$eval.ts").unwrap(); let global_state = GlobalState::new(flags)?; - let mut worker = create_main_worker(global_state, main_module.clone())?; + let mut worker = MainWorker::create(global_state, main_module.clone())?; let main_module_url = main_module.as_url().to_owned(); // Create a dummy source file. let source_file = SourceFile { @@ -388,43 +374,78 @@ async fn bundle_command( source_file: String, out_file: Option, ) -> Result<(), ErrBox> { - let mut module_name = ModuleSpecifier::resolve_url_or_path(&source_file)?; - let url = module_name.as_url(); + let mut module_specifier = + ModuleSpecifier::resolve_url_or_path(&source_file)?; + let url = module_specifier.as_url(); // TODO(bartlomieju): fix this hack in ModuleSpecifier if url.scheme() == "file" { let a = deno_fs::normalize_path(&url.to_file_path().unwrap()); let u = Url::from_file_path(a).unwrap(); - module_name = ModuleSpecifier::from(u) + module_specifier = ModuleSpecifier::from(u) } debug!(">>>>> bundle START"); let compiler_config = tsc::CompilerConfig::load(flags.config_path.clone())?; - let maybe_import_map = match flags.import_map_path.as_ref() { - None => None, - Some(file_path) => { - if !flags.unstable { - exit_unstable("--importmap") - } - Some(ImportMap::load(file_path)?) - } - }; - let global_state = GlobalState::new(flags)?; - let bundle_result = tsc::bundle( + info!("Bundling {}", module_specifier.to_string()); + + let output = tsc::bundle( &global_state, compiler_config, - module_name, - maybe_import_map, - out_file, + module_specifier, + global_state.maybe_import_map.clone(), global_state.flags.unstable, ) - .await; + .await?; debug!(">>>>> bundle END"); - bundle_result + + if let Some(out_file_) = out_file.as_ref() { + info!("Emitting bundle to {:?}", out_file_); + let output_bytes = output.as_bytes(); + let output_len = output_bytes.len(); + deno_fs::write_file(out_file_, output_bytes, 0o666)?; + info!("{} emitted.", human_size(output_len as f64)); + } else { + println!("{}", output); + } + Ok(()) +} + +fn human_size(bytse: f64) -> String { + let negative = if bytse.is_sign_positive() { "" } else { "-" }; + let bytse = bytse.abs(); + let units = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]; + if bytse < 1_f64 { + return format!("{}{} {}", negative, bytse, "Bytes"); + } + let delimiter = 1024_f64; + let exponent = std::cmp::min( + (bytse.ln() / delimiter.ln()).floor() as i32, + (units.len() - 1) as i32, + ); + let pretty_bytes = format!("{:.2}", bytse / delimiter.powi(exponent)) + .parse::() + .unwrap() + * 1_f64; + let unit = units[exponent as usize]; + format!("{}{} {}", negative, pretty_bytes, unit) +} + +#[test] +fn human_size_test() { + assert_eq!(human_size(16_f64), "16 Bytes"); + assert_eq!(human_size((16 * 1024) as f64), "16 KB"); + assert_eq!(human_size((16 * 1024 * 1024) as f64), "16 MB"); + assert_eq!(human_size(16_f64 * 1024_f64.powf(3.0)), "16 GB"); + assert_eq!(human_size(16_f64 * 1024_f64.powf(4.0)), "16 TB"); + assert_eq!(human_size(16_f64 * 1024_f64.powf(5.0)), "16 PB"); + assert_eq!(human_size(16_f64 * 1024_f64.powf(6.0)), "16 EB"); + assert_eq!(human_size(16_f64 * 1024_f64.powf(7.0)), "16 ZB"); + assert_eq!(human_size(16_f64 * 1024_f64.powf(8.0)), "16 YB"); } async fn doc_command( @@ -501,7 +522,7 @@ async fn run_repl(flags: Flags) -> Result<(), ErrBox> { let main_module = ModuleSpecifier::resolve_url_or_path("./__$deno$repl.ts").unwrap(); let global_state = GlobalState::new(flags)?; - let mut worker = create_main_worker(global_state, main_module)?; + let mut worker = MainWorker::create(global_state, main_module)?; loop { (&mut *worker).await?; } @@ -511,21 +532,13 @@ async fn run_command(flags: Flags, script: String) -> Result<(), ErrBox> { let global_state = GlobalState::new(flags.clone())?; let main_module = ModuleSpecifier::resolve_url_or_path(&script).unwrap(); let mut worker = - create_main_worker(global_state.clone(), main_module.clone())?; + MainWorker::create(global_state.clone(), main_module.clone())?; debug!("main_module {}", main_module); worker.execute_module(&main_module).await?; + write_lockfile(global_state)?; worker.execute("window.dispatchEvent(new Event('load'))")?; (&mut *worker).await?; worker.execute("window.dispatchEvent(new Event('unload'))")?; - if global_state.flags.lock_write { - if let Some(ref lockfile) = global_state.lockfile { - let g = lockfile.lock().unwrap(); - g.write()?; - } else { - eprintln!("--lock flag must be specified when using --lock-write"); - std::process::exit(11); - } - } Ok(()) } @@ -558,7 +571,7 @@ async fn test_command( let main_module = ModuleSpecifier::resolve_url(&test_file_url.to_string()).unwrap(); let mut worker = - create_main_worker(global_state.clone(), main_module.clone())?; + MainWorker::create(global_state.clone(), main_module.clone())?; // Create a dummy source file. let source_file = SourceFile { filename: test_file_url.to_file_path().unwrap(), diff --git a/cli/module_graph.rs b/cli/module_graph.rs index be3bd288462804..519f443ff991d5 100644 --- a/cli/module_graph.rs +++ b/cli/module_graph.rs @@ -1,5 +1,6 @@ // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. +use crate::doc::Location; use crate::file_fetcher::map_file_extension; use crate::file_fetcher::SourceFile; use crate::file_fetcher::SourceFileFetcher; @@ -9,7 +10,7 @@ use crate::op_error::OpError; use crate::permissions::Permissions; use crate::swc_util::analyze_dependencies_and_references; use crate::swc_util::TsReferenceKind; -use crate::tsc::get_available_libs; +use crate::tsc::AVAILABLE_LIBS; use deno_core::ErrBox; use deno_core::ModuleSpecifier; use futures::stream::FuturesUnordered; @@ -24,6 +25,18 @@ use std::hash::BuildHasher; use std::path::PathBuf; use std::pin::Pin; +// TODO(bartlomieju): it'd be great if this function returned +// more structured data and possibly format the same as TS diagnostics. +/// Decorate error with location of import that caused the error. +fn err_with_location(e: ErrBox, location: &Location) -> ErrBox { + let location_str = format!( + "\nImported from \"{}:{}\"", + location.filename, location.line + ); + let err_str = e.to_string(); + OpError::other(format!("{}{}", err_str, location_str)).into() +} + fn serialize_module_specifier( spec: &ModuleSpecifier, s: S, @@ -138,8 +151,9 @@ impl ModuleGraphLoader { pub async fn add_to_graph( &mut self, specifier: &ModuleSpecifier, + maybe_referrer: Option, ) -> Result<(), ErrBox> { - self.download_module(specifier.clone(), None)?; + self.download_module(specifier.clone(), maybe_referrer)?; loop { let (specifier, source_file) = @@ -239,10 +253,8 @@ impl ModuleGraphLoader { imports.push(import_descriptor); } - let available_libs = get_available_libs(); - for ref_desc in ref_descs { - if available_libs.contains(&ref_desc.specifier) { + if AVAILABLE_LIBS.contains(&ref_desc.specifier.as_str()) { continue; } @@ -446,31 +458,33 @@ impl ModuleGraphLoader { let import_descriptor = ImportDescriptor { specifier: import_desc.specifier.to_string(), resolved_specifier, - type_directive: import_desc.deno_types, + type_directive: import_desc.deno_types.clone(), resolved_type_directive, }; - self.download_module( - import_descriptor.resolved_specifier.clone(), - Some(module_specifier.clone()), - )?; + self + .download_module( + import_descriptor.resolved_specifier.clone(), + Some(module_specifier.clone()), + ) + .map_err(|e| err_with_location(e, &import_desc.location))?; if let Some(type_dir_url) = import_descriptor.resolved_type_directive.as_ref() { - self.download_module( - type_dir_url.clone(), - Some(module_specifier.clone()), - )?; + self + .download_module( + type_dir_url.clone(), + Some(module_specifier.clone()), + ) + .map_err(|e| err_with_location(e, &import_desc.location))?; } imports.push(import_descriptor); } - let available_libs = get_available_libs(); - for ref_desc in ref_descs { - if available_libs.contains(&ref_desc.specifier) { + if AVAILABLE_LIBS.contains(&ref_desc.specifier.as_str()) { continue; } @@ -484,10 +498,12 @@ impl ModuleGraphLoader { resolved_specifier, }; - self.download_module( - reference_descriptor.resolved_specifier.clone(), - Some(module_specifier.clone()), - )?; + self + .download_module( + reference_descriptor.resolved_specifier.clone(), + Some(module_specifier.clone()), + ) + .map_err(|e| err_with_location(e, &ref_desc.location))?; match ref_desc.kind { TsReferenceKind::Lib => { @@ -539,7 +555,7 @@ mod tests { false, false, ); - graph_loader.add_to_graph(&module_specifier).await?; + graph_loader.add_to_graph(&module_specifier, None).await?; Ok(graph_loader.get_graph()) } diff --git a/cli/ops/dispatch_json.rs b/cli/ops/dispatch_json.rs index 6125ea39bb14bb..97a02991f5b169 100644 --- a/cli/ops/dispatch_json.rs +++ b/cli/ops/dispatch_json.rs @@ -1,7 +1,7 @@ // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. use crate::op_error::OpError; use deno_core::Buf; -use deno_core::CoreIsolate; +use deno_core::CoreIsolateState; use deno_core::Op; use deno_core::ZeroCopyBuf; use futures::future::FutureExt; @@ -46,14 +46,17 @@ struct AsyncArgs { pub fn json_op( d: D, -) -> impl Fn(&mut CoreIsolate, &[u8], Option) -> Op +) -> impl Fn(&mut CoreIsolateState, &[u8], &mut [ZeroCopyBuf]) -> Op where - D: - Fn(&mut CoreIsolate, Value, Option) -> Result, + D: Fn( + &mut CoreIsolateState, + Value, + &mut [ZeroCopyBuf], + ) -> Result, { - move |isolate: &mut CoreIsolate, + move |isolate_state: &mut CoreIsolateState, control: &[u8], - zero_copy: Option| { + zero_copy: &mut [ZeroCopyBuf]| { let async_args: AsyncArgs = match serde_json::from_slice(control) { Ok(args) => args, Err(e) => { @@ -66,7 +69,7 @@ where let result = serde_json::from_slice(control) .map_err(OpError::from) - .and_then(|args| d(isolate, args, zero_copy)); + .and_then(|args| d(isolate_state, args, zero_copy)); // Convert to Op match result { diff --git a/cli/ops/dispatch_minimal.rs b/cli/ops/dispatch_minimal.rs index ac98ea5896486e..eac5ad05573358 100644 --- a/cli/ops/dispatch_minimal.rs +++ b/cli/ops/dispatch_minimal.rs @@ -7,7 +7,7 @@ use crate::op_error::OpError; use byteorder::{LittleEndian, WriteBytesExt}; use deno_core::Buf; -use deno_core::CoreIsolate; +use deno_core::CoreIsolateState; use deno_core::Op; use deno_core::ZeroCopyBuf; use futures::future::FutureExt; @@ -116,13 +116,13 @@ fn test_parse_min_record() { pub fn minimal_op( d: D, -) -> impl Fn(&mut CoreIsolate, &[u8], Option) -> Op +) -> impl Fn(&mut CoreIsolateState, &[u8], &mut [ZeroCopyBuf]) -> Op where - D: Fn(&mut CoreIsolate, bool, i32, Option) -> MinimalOp, + D: Fn(&mut CoreIsolateState, bool, i32, &mut [ZeroCopyBuf]) -> MinimalOp, { - move |isolate: &mut CoreIsolate, + move |isolate_state: &mut CoreIsolateState, control: &[u8], - zero_copy: Option| { + zero_copy: &mut [ZeroCopyBuf]| { let mut record = match parse_min_record(control) { Some(r) => r, None => { @@ -138,7 +138,7 @@ where }; let is_sync = record.promise_id == 0; let rid = record.arg; - let min_op = d(isolate, is_sync, rid, zero_copy); + let min_op = d(isolate_state, is_sync, rid, zero_copy); match min_op { MinimalOp::Sync(sync_result) => Op::Sync(match sync_result { diff --git a/cli/ops/errors.rs b/cli/ops/errors.rs index 766c130e2c2cd9..ade125b1a1552d 100644 --- a/cli/ops/errors.rs +++ b/cli/ops/errors.rs @@ -31,7 +31,7 @@ struct ApplySourceMap { fn op_apply_source_map( state: &State, args: Value, - _zero_copy: Option, + _zero_copy: &mut [ZeroCopyBuf], ) -> Result { let args: ApplySourceMap = serde_json::from_value(args)?; @@ -55,7 +55,7 @@ fn op_apply_source_map( fn op_format_diagnostic( _state: &State, args: Value, - _zero_copy: Option, + _zero_copy: &mut [ZeroCopyBuf], ) -> Result { let diagnostic = serde_json::from_value::(args)?; Ok(JsonOp::Sync(json!(diagnostic.to_string()))) diff --git a/cli/ops/fetch.rs b/cli/ops/fetch.rs index 596c9a2fd2058d..5a646325edb6c3 100644 --- a/cli/ops/fetch.rs +++ b/cli/ops/fetch.rs @@ -5,6 +5,7 @@ use crate::http_util::{create_http_client, HttpBody}; use crate::op_error::OpError; use crate::state::State; use deno_core::CoreIsolate; +use deno_core::CoreIsolateState; use deno_core::ZeroCopyBuf; use futures::future::FutureExt; use http::header::HeaderName; @@ -24,10 +25,10 @@ struct FetchArgs { } pub fn op_fetch( - isolate: &mut CoreIsolate, + isolate_state: &mut CoreIsolateState, state: &State, args: Value, - data: Option, + data: &mut [ZeroCopyBuf], ) -> Result { let args: FetchArgs = serde_json::from_value(args)?; let url = args.url; @@ -56,8 +57,10 @@ pub fn op_fetch( let mut request = client.request(method, url_); - if let Some(buf) = data { - request = request.body(Vec::from(&*buf)); + match data.len() { + 0 => {} + 1 => request = request.body(Vec::from(&*data[0])), + _ => panic!("Invalid number of arguments"), } for (key, value) in args.headers { @@ -67,7 +70,7 @@ pub fn op_fetch( } debug!("Before fetch {}", url); - let resource_table = isolate.resource_table.clone(); + let resource_table = isolate_state.resource_table.clone(); let future = async move { let res = request.send().await?; debug!("Fetch response {}", url); diff --git a/cli/ops/fs.rs b/cli/ops/fs.rs index 9d5be30778ac2f..63b3ad7f56ea7b 100644 --- a/cli/ops/fs.rs +++ b/cli/ops/fs.rs @@ -3,11 +3,11 @@ use super::dispatch_json::{blocking_json, Deserialize, JsonOp, Value}; use super::io::std_file_resource; use super::io::{FileMetadata, StreamResource, StreamResourceHolder}; -use crate::fs::resolve_from_cwd; use crate::op_error::OpError; use crate::ops::dispatch_json::JsonResult; use crate::state::State; use deno_core::CoreIsolate; +use deno_core::CoreIsolateState; use deno_core::ZeroCopyBuf; use futures::future::FutureExt; use std::convert::From; @@ -69,14 +69,14 @@ struct OpenOptions { } fn op_open( - isolate: &mut CoreIsolate, + isolate_state: &mut CoreIsolateState, state: &State, args: Value, - _zero_copy: Option, + _zero_copy: &mut [ZeroCopyBuf], ) -> Result { let args: OpenArgs = serde_json::from_value(args)?; - let path = resolve_from_cwd(Path::new(&args.path))?; - let resource_table = isolate.resource_table.clone(); + let path = Path::new(&args.path).to_path_buf(); + let resource_table = isolate_state.resource_table.clone(); let mut open_options = std::fs::OpenOptions::new(); @@ -152,10 +152,10 @@ struct SeekArgs { } fn op_seek( - isolate: &mut CoreIsolate, + isolate_state: &mut CoreIsolateState, _state: &State, args: Value, - _zero_copy: Option, + _zero_copy: &mut [ZeroCopyBuf], ) -> Result { use std::io::{Seek, SeekFrom}; let args: SeekArgs = serde_json::from_value(args)?; @@ -175,7 +175,7 @@ fn op_seek( } }; - let resource_table = isolate.resource_table.clone(); + let resource_table = isolate_state.resource_table.clone(); let is_sync = args.promise_id.is_none(); if is_sync { @@ -212,7 +212,7 @@ struct UmaskArgs { fn op_umask( state: &State, args: Value, - _zero_copy: Option, + _zero_copy: &mut [ZeroCopyBuf], ) -> Result { state.check_unstable("Deno.umask"); let args: UmaskArgs = serde_json::from_value(args)?; @@ -250,7 +250,7 @@ struct ChdirArgs { fn op_chdir( state: &State, args: Value, - _zero_copy: Option, + _zero_copy: &mut [ZeroCopyBuf], ) -> Result { let args: ChdirArgs = serde_json::from_value(args)?; let d = PathBuf::from(&args.directory); @@ -271,10 +271,10 @@ struct MkdirArgs { fn op_mkdir( state: &State, args: Value, - _zero_copy: Option, + _zero_copy: &mut [ZeroCopyBuf], ) -> Result { let args: MkdirArgs = serde_json::from_value(args)?; - let path = resolve_from_cwd(Path::new(&args.path))?; + let path = Path::new(&args.path).to_path_buf(); let mode = args.mode.unwrap_or(0o777) & 0o777; state.check_write(&path)?; @@ -305,10 +305,10 @@ struct ChmodArgs { fn op_chmod( state: &State, args: Value, - _zero_copy: Option, + _zero_copy: &mut [ZeroCopyBuf], ) -> Result { let args: ChmodArgs = serde_json::from_value(args)?; - let path = resolve_from_cwd(Path::new(&args.path))?; + let path = Path::new(&args.path).to_path_buf(); let mode = args.mode & 0o777; state.check_write(&path)?; @@ -345,10 +345,10 @@ struct ChownArgs { fn op_chown( state: &State, args: Value, - _zero_copy: Option, + _zero_copy: &mut [ZeroCopyBuf], ) -> Result { let args: ChownArgs = serde_json::from_value(args)?; - let path = resolve_from_cwd(Path::new(&args.path))?; + let path = Path::new(&args.path).to_path_buf(); state.check_write(&path)?; @@ -384,10 +384,10 @@ struct RemoveArgs { fn op_remove( state: &State, args: Value, - _zero_copy: Option, + _zero_copy: &mut [ZeroCopyBuf], ) -> Result { let args: RemoveArgs = serde_json::from_value(args)?; - let path = resolve_from_cwd(Path::new(&args.path))?; + let path = PathBuf::from(&args.path); let recursive = args.recursive; state.check_write(&path)?; @@ -417,8 +417,11 @@ fn op_remove( std::fs::remove_file(&path)?; } } - } else { + } else if file_type.is_dir() { std::fs::remove_dir(&path)?; + } else { + // pipes, sockets, etc... + std::fs::remove_file(&path)?; } Ok(json!({})) }) @@ -435,11 +438,11 @@ struct CopyFileArgs { fn op_copy_file( state: &State, args: Value, - _zero_copy: Option, + _zero_copy: &mut [ZeroCopyBuf], ) -> Result { let args: CopyFileArgs = serde_json::from_value(args)?; - let from = resolve_from_cwd(Path::new(&args.from))?; - let to = resolve_from_cwd(Path::new(&args.to))?; + let from = PathBuf::from(&args.from); + let to = PathBuf::from(&args.to); state.check_read(&from)?; state.check_write(&to)?; @@ -529,10 +532,10 @@ struct StatArgs { fn op_stat( state: &State, args: Value, - _zero_copy: Option, + _zero_copy: &mut [ZeroCopyBuf], ) -> Result { let args: StatArgs = serde_json::from_value(args)?; - let path = resolve_from_cwd(Path::new(&args.path))?; + let path = PathBuf::from(&args.path); let lstat = args.lstat; state.check_read(&path)?; @@ -559,12 +562,15 @@ struct RealpathArgs { fn op_realpath( state: &State, args: Value, - _zero_copy: Option, + _zero_copy: &mut [ZeroCopyBuf], ) -> Result { let args: RealpathArgs = serde_json::from_value(args)?; - let path = resolve_from_cwd(Path::new(&args.path))?; + let path = PathBuf::from(&args.path); state.check_read(&path)?; + if path.is_relative() { + state.check_read_blind(¤t_dir()?, "CWD")?; + } let is_sync = args.promise_id.is_none(); blocking_json(is_sync, move || { @@ -591,10 +597,10 @@ struct ReadDirArgs { fn op_read_dir( state: &State, args: Value, - _zero_copy: Option, + _zero_copy: &mut [ZeroCopyBuf], ) -> Result { let args: ReadDirArgs = serde_json::from_value(args)?; - let path = resolve_from_cwd(Path::new(&args.path))?; + let path = PathBuf::from(&args.path); state.check_read(&path)?; @@ -634,11 +640,11 @@ struct RenameArgs { fn op_rename( state: &State, args: Value, - _zero_copy: Option, + _zero_copy: &mut [ZeroCopyBuf], ) -> Result { let args: RenameArgs = serde_json::from_value(args)?; - let oldpath = resolve_from_cwd(Path::new(&args.oldpath))?; - let newpath = resolve_from_cwd(Path::new(&args.newpath))?; + let oldpath = PathBuf::from(&args.oldpath); + let newpath = PathBuf::from(&args.newpath); state.check_read(&oldpath)?; state.check_write(&oldpath)?; @@ -663,12 +669,12 @@ struct LinkArgs { fn op_link( state: &State, args: Value, - _zero_copy: Option, + _zero_copy: &mut [ZeroCopyBuf], ) -> Result { state.check_unstable("Deno.link"); let args: LinkArgs = serde_json::from_value(args)?; - let oldpath = resolve_from_cwd(Path::new(&args.oldpath))?; - let newpath = resolve_from_cwd(Path::new(&args.newpath))?; + let oldpath = PathBuf::from(&args.oldpath); + let newpath = PathBuf::from(&args.newpath); state.check_read(&oldpath)?; state.check_write(&newpath)?; @@ -701,12 +707,12 @@ struct SymlinkOptions { fn op_symlink( state: &State, args: Value, - _zero_copy: Option, + _zero_copy: &mut [ZeroCopyBuf], ) -> Result { state.check_unstable("Deno.symlink"); let args: SymlinkArgs = serde_json::from_value(args)?; - let oldpath = resolve_from_cwd(Path::new(&args.oldpath))?; - let newpath = resolve_from_cwd(Path::new(&args.newpath))?; + let oldpath = PathBuf::from(&args.oldpath); + let newpath = PathBuf::from(&args.newpath); state.check_write(&newpath)?; @@ -761,10 +767,10 @@ struct ReadLinkArgs { fn op_read_link( state: &State, args: Value, - _zero_copy: Option, + _zero_copy: &mut [ZeroCopyBuf], ) -> Result { let args: ReadLinkArgs = serde_json::from_value(args)?; - let path = resolve_from_cwd(Path::new(&args.path))?; + let path = PathBuf::from(&args.path); state.check_read(&path)?; @@ -788,10 +794,10 @@ struct TruncateArgs { fn op_truncate( state: &State, args: Value, - _zero_copy: Option, + _zero_copy: &mut [ZeroCopyBuf], ) -> Result { let args: TruncateArgs = serde_json::from_value(args)?; - let path = resolve_from_cwd(Path::new(&args.path))?; + let path = PathBuf::from(&args.path); let len = args.len; state.check_write(&path)?; @@ -862,11 +868,11 @@ struct MakeTempArgs { fn op_make_temp_dir( state: &State, args: Value, - _zero_copy: Option, + _zero_copy: &mut [ZeroCopyBuf], ) -> Result { let args: MakeTempArgs = serde_json::from_value(args)?; - let dir = args.dir.map(|s| resolve_from_cwd(Path::new(&s)).unwrap()); + let dir = args.dir.map(|s| PathBuf::from(&s)); let prefix = args.prefix.map(String::from); let suffix = args.suffix.map(String::from); @@ -893,11 +899,11 @@ fn op_make_temp_dir( fn op_make_temp_file( state: &State, args: Value, - _zero_copy: Option, + _zero_copy: &mut [ZeroCopyBuf], ) -> Result { let args: MakeTempArgs = serde_json::from_value(args)?; - let dir = args.dir.map(|s| resolve_from_cwd(Path::new(&s)).unwrap()); + let dir = args.dir.map(|s| PathBuf::from(&s)); let prefix = args.prefix.map(String::from); let suffix = args.suffix.map(String::from); @@ -926,19 +932,19 @@ fn op_make_temp_file( struct UtimeArgs { promise_id: Option, path: String, - atime: u64, - mtime: u64, + atime: i64, + mtime: i64, } fn op_utime( state: &State, args: Value, - _zero_copy: Option, + _zero_copy: &mut [ZeroCopyBuf], ) -> Result { state.check_unstable("Deno.utime"); let args: UtimeArgs = serde_json::from_value(args)?; - let path = resolve_from_cwd(Path::new(&args.path))?; + let path = PathBuf::from(&args.path); state.check_write(&path)?; @@ -953,10 +959,10 @@ fn op_utime( fn op_cwd( state: &State, _args: Value, - _zero_copy: Option, + _zero_copy: &mut [ZeroCopyBuf], ) -> Result { let path = current_dir()?; - state.check_read(&path)?; + state.check_read_blind(&path, "CWD")?; let path_str = into_string(path.into_os_string())?; Ok(JsonOp::Sync(json!(path_str))) } diff --git a/cli/ops/fs_events.rs b/cli/ops/fs_events.rs index 56ed556f4f7a2a..b9d54fc3e5bd6e 100644 --- a/cli/ops/fs_events.rs +++ b/cli/ops/fs_events.rs @@ -3,6 +3,7 @@ use super::dispatch_json::{Deserialize, JsonOp, Value}; use crate::op_error::OpError; use crate::state::State; use deno_core::CoreIsolate; +use deno_core::CoreIsolateState; use deno_core::ErrBox; use deno_core::ZeroCopyBuf; use futures::future::poll_fn; @@ -62,10 +63,10 @@ impl From for FsEvent { } pub fn op_fs_events_open( - isolate: &mut CoreIsolate, + isolate_state: &mut CoreIsolateState, state: &State, args: Value, - _zero_copy: Option, + _zero_copy: &mut [ZeroCopyBuf], ) -> Result { #[derive(Deserialize)] struct OpenArgs { @@ -94,23 +95,23 @@ pub fn op_fs_events_open( watcher.watch(path, recursive_mode).map_err(ErrBox::from)?; } let resource = FsEventsResource { watcher, receiver }; - let mut resource_table = isolate.resource_table.borrow_mut(); + let mut resource_table = isolate_state.resource_table.borrow_mut(); let rid = resource_table.add("fsEvents", Box::new(resource)); Ok(JsonOp::Sync(json!(rid))) } pub fn op_fs_events_poll( - isolate: &mut CoreIsolate, + isolate_state: &mut CoreIsolateState, _state: &State, args: Value, - _zero_copy: Option, + _zero_copy: &mut [ZeroCopyBuf], ) -> Result { #[derive(Deserialize)] struct PollArgs { rid: u32, } let PollArgs { rid } = serde_json::from_value(args)?; - let resource_table = isolate.resource_table.clone(); + let resource_table = isolate_state.resource_table.clone(); let f = poll_fn(move |cx| { let mut resource_table = resource_table.borrow_mut(); let watcher = resource_table diff --git a/cli/ops/io.rs b/cli/ops/io.rs index 705083e15cacbf..0e007ad1a5f775 100644 --- a/cli/ops/io.rs +++ b/cli/ops/io.rs @@ -3,6 +3,7 @@ use crate::http_util::HttpBody; use crate::op_error::OpError; use crate::state::State; use deno_core::CoreIsolate; +use deno_core::CoreIsolateState; use deno_core::ResourceTable; use deno_core::ZeroCopyBuf; use futures::future::poll_fn; @@ -206,19 +207,19 @@ impl DenoAsyncRead for StreamResource { } pub fn op_read( - isolate: &mut CoreIsolate, + isolate_state: &mut CoreIsolateState, _state: &State, is_sync: bool, rid: i32, - zero_copy: Option, + zero_copy: &mut [ZeroCopyBuf], ) -> MinimalOp { debug!("read rid={}", rid); - if zero_copy.is_none() { - return MinimalOp::Sync(Err(no_buffer_specified())); + match zero_copy.len() { + 0 => return MinimalOp::Sync(Err(no_buffer_specified())), + 1 => {} + _ => panic!("Invalid number of arguments"), } - let resource_table = isolate.resource_table.clone(); - - let mut buf = zero_copy.unwrap(); + let resource_table = isolate_state.resource_table.clone(); if is_sync { MinimalOp::Sync({ @@ -228,7 +229,7 @@ pub fn op_read( Ok(std_file) => { use std::io::Read; std_file - .read(&mut buf) + .read(&mut zero_copy[0]) .map(|n: usize| n as i32) .map_err(OpError::from) } @@ -238,6 +239,7 @@ pub fn op_read( }) }) } else { + let mut zero_copy = zero_copy[0].clone(); MinimalOp::Async( poll_fn(move |cx| { let mut resource_table = resource_table.borrow_mut(); @@ -248,7 +250,7 @@ pub fn op_read( let mut task_tracker_id: Option = None; let nread = match resource_holder .resource - .poll_read(cx, &mut buf.as_mut()[..]) + .poll_read(cx, &mut zero_copy) .map_err(OpError::from) { Poll::Ready(t) => { @@ -330,28 +332,28 @@ impl DenoAsyncWrite for StreamResource { } pub fn op_write( - isolate: &mut CoreIsolate, + isolate_state: &mut CoreIsolateState, _state: &State, is_sync: bool, rid: i32, - zero_copy: Option, + zero_copy: &mut [ZeroCopyBuf], ) -> MinimalOp { debug!("write rid={}", rid); - if zero_copy.is_none() { - return MinimalOp::Sync(Err(no_buffer_specified())); + match zero_copy.len() { + 0 => return MinimalOp::Sync(Err(no_buffer_specified())), + 1 => {} + _ => panic!("Invalid number of arguments"), } - let buf = zero_copy.unwrap(); - if is_sync { MinimalOp::Sync({ // First we look up the rid in the resource table. - let mut resource_table = isolate.resource_table.borrow_mut(); + let mut resource_table = isolate_state.resource_table.borrow_mut(); std_file_resource(&mut resource_table, rid as u32, move |r| match r { Ok(std_file) => { use std::io::Write; std_file - .write(&buf) + .write(&zero_copy[0]) .map(|nwritten: usize| nwritten as i32) .map_err(OpError::from) } @@ -361,7 +363,8 @@ pub fn op_write( }) }) } else { - let resource_table = isolate.resource_table.clone(); + let zero_copy = zero_copy[0].clone(); + let resource_table = isolate_state.resource_table.clone(); MinimalOp::Async( async move { let nwritten = poll_fn(|cx| { @@ -369,7 +372,7 @@ pub fn op_write( let resource_holder = resource_table .get_mut::(rid as u32) .ok_or_else(OpError::bad_resource_id)?; - resource_holder.resource.poll_write(cx, &buf.as_ref()[..]) + resource_holder.resource.poll_write(cx, &zero_copy) }) .await?; diff --git a/cli/ops/net.rs b/cli/ops/net.rs index ae5bcb9bba568e..5ccb62d6c807d0 100644 --- a/cli/ops/net.rs +++ b/cli/ops/net.rs @@ -5,6 +5,7 @@ use crate::op_error::OpError; use crate::resolve_addr::resolve_addr; use crate::state::State; use deno_core::CoreIsolate; +use deno_core::CoreIsolateState; use deno_core::ResourceTable; use deno_core::ZeroCopyBuf; use futures::future::poll_fn; @@ -37,12 +38,12 @@ struct AcceptArgs { } fn accept_tcp( - isolate: &mut CoreIsolate, + isolate_state: &mut CoreIsolateState, args: AcceptArgs, - _zero_copy: Option, + _zero_copy: &mut [ZeroCopyBuf], ) -> Result { let rid = args.rid as u32; - let resource_table = isolate.resource_table.clone(); + let resource_table = isolate_state.resource_table.clone(); let op = async move { let accept_fut = poll_fn(|cx| { @@ -97,16 +98,16 @@ fn accept_tcp( } fn op_accept( - isolate: &mut CoreIsolate, + isolate_state: &mut CoreIsolateState, _state: &State, args: Value, - zero_copy: Option, + zero_copy: &mut [ZeroCopyBuf], ) -> Result { let args: AcceptArgs = serde_json::from_value(args)?; match args.transport.as_str() { - "tcp" => accept_tcp(isolate, args, zero_copy), + "tcp" => accept_tcp(isolate_state, args, zero_copy), #[cfg(unix)] - "unix" => net_unix::accept_unix(isolate, args.rid as u32, zero_copy), + "unix" => net_unix::accept_unix(isolate_state, args.rid as u32, zero_copy), _ => Err(OpError::other(format!( "Unsupported transport protocol {}", args.transport @@ -121,16 +122,17 @@ struct ReceiveArgs { } fn receive_udp( - isolate: &mut CoreIsolate, + isolate_state: &mut CoreIsolateState, _state: &State, args: ReceiveArgs, - zero_copy: Option, + zero_copy: &mut [ZeroCopyBuf], ) -> Result { - let mut buf = zero_copy.unwrap(); + assert_eq!(zero_copy.len(), 1, "Invalid number of arguments"); + let mut zero_copy = zero_copy[0].clone(); let rid = args.rid as u32; - let resource_table = isolate.resource_table.clone(); + let resource_table = isolate_state.resource_table.clone(); let op = async move { let receive_fut = poll_fn(|cx| { @@ -141,7 +143,9 @@ fn receive_udp( OpError::bad_resource("Socket has been closed".to_string()) })?; let socket = &mut resource.socket; - socket.poll_recv_from(cx, &mut buf).map_err(OpError::from) + socket + .poll_recv_from(cx, &mut zero_copy) + .map_err(OpError::from) }); let (size, remote_addr) = receive_fut.await?; Ok(json!({ @@ -158,18 +162,19 @@ fn receive_udp( } fn op_receive( - isolate: &mut CoreIsolate, + isolate_state: &mut CoreIsolateState, state: &State, args: Value, - zero_copy: Option, + zero_copy: &mut [ZeroCopyBuf], ) -> Result { - assert!(zero_copy.is_some()); + assert_eq!(zero_copy.len(), 1, "Invalid number of arguments"); + let args: ReceiveArgs = serde_json::from_value(args)?; match args.transport.as_str() { - "udp" => receive_udp(isolate, state, args, zero_copy), + "udp" => receive_udp(isolate_state, state, args, zero_copy), #[cfg(unix)] "unixpacket" => { - net_unix::receive_unix_packet(isolate, args.rid as u32, zero_copy) + net_unix::receive_unix_packet(isolate_state, args.rid as u32, zero_copy) } _ => Err(OpError::other(format!( "Unsupported transport protocol {}", @@ -187,14 +192,15 @@ struct SendArgs { } fn op_send( - isolate: &mut CoreIsolate, + isolate_state: &mut CoreIsolateState, state: &State, args: Value, - zero_copy: Option, + zero_copy: &mut [ZeroCopyBuf], ) -> Result { - assert!(zero_copy.is_some()); - let buf = zero_copy.unwrap(); - let resource_table = isolate.resource_table.clone(); + assert_eq!(zero_copy.len(), 1, "Invalid number of arguments"); + let zero_copy = zero_copy[0].clone(); + + let resource_table = isolate_state.resource_table.clone(); match serde_json::from_value(args)? { SendArgs { rid, @@ -212,7 +218,7 @@ fn op_send( })?; let socket = &mut resource.socket; let addr = resolve_addr(&args.hostname, args.port)?; - socket.send_to(&buf, addr).await?; + socket.send_to(&zero_copy, addr).await?; Ok(json!({})) }; @@ -236,7 +242,7 @@ fn op_send( let socket = &mut resource.socket; socket - .send_to(&buf, &resource.local_addr.as_pathname().unwrap()) + .send_to(&zero_copy, &resource.local_addr.as_pathname().unwrap()) .await?; Ok(json!({})) @@ -256,12 +262,12 @@ struct ConnectArgs { } fn op_connect( - isolate: &mut CoreIsolate, + isolate_state: &mut CoreIsolateState, state: &State, args: Value, - _zero_copy: Option, + _zero_copy: &mut [ZeroCopyBuf], ) -> Result { - let resource_table = isolate.resource_table.clone(); + let resource_table = isolate_state.resource_table.clone(); match serde_json::from_value(args)? { ConnectArgs { transport, @@ -342,10 +348,10 @@ struct ShutdownArgs { } fn op_shutdown( - isolate: &mut CoreIsolate, + isolate_state: &mut CoreIsolateState, state: &State, args: Value, - _zero_copy: Option, + _zero_copy: &mut [ZeroCopyBuf], ) -> Result { state.check_unstable("Deno.shutdown"); @@ -360,7 +366,7 @@ fn op_shutdown( _ => unimplemented!(), }; - let mut resource_table = isolate.resource_table.borrow_mut(); + let mut resource_table = isolate_state.resource_table.borrow_mut(); let resource_holder = resource_table .get_mut::(rid) .ok_or_else(OpError::bad_resource_id)?; @@ -484,12 +490,12 @@ fn listen_udp( } fn op_listen( - isolate: &mut CoreIsolate, + isolate_state: &mut CoreIsolateState, state: &State, args: Value, - _zero_copy: Option, + _zero_copy: &mut [ZeroCopyBuf], ) -> Result { - let mut resource_table = isolate.resource_table.borrow_mut(); + let mut resource_table = isolate_state.resource_table.borrow_mut(); match serde_json::from_value(args)? { ListenArgs { transport, diff --git a/cli/ops/net_unix.rs b/cli/ops/net_unix.rs index 4b09c2fdb2fc4b..0b2ceb75a1d6bc 100644 --- a/cli/ops/net_unix.rs +++ b/cli/ops/net_unix.rs @@ -1,7 +1,7 @@ use super::dispatch_json::{Deserialize, JsonOp}; use super::io::{StreamResource, StreamResourceHolder}; use crate::op_error::OpError; -use deno_core::CoreIsolate; +use deno_core::CoreIsolateState; use deno_core::ResourceTable; use deno_core::ZeroCopyBuf; use futures::future::FutureExt; @@ -27,11 +27,11 @@ pub struct UnixListenArgs { } pub fn accept_unix( - isolate: &mut CoreIsolate, + isolate_state: &mut CoreIsolateState, rid: u32, - _zero_copy: Option, + _zero_copy: &mut [ZeroCopyBuf], ) -> Result { - let resource_table = isolate.resource_table.clone(); + let resource_table = isolate_state.resource_table.clone(); { let _ = resource_table .borrow() @@ -78,12 +78,13 @@ pub fn accept_unix( } pub fn receive_unix_packet( - isolate: &mut CoreIsolate, + isolate_state: &mut CoreIsolateState, rid: u32, - zero_copy: Option, + zero_copy: &mut [ZeroCopyBuf], ) -> Result { - let mut buf = zero_copy.unwrap(); - let resource_table = isolate.resource_table.clone(); + assert_eq!(zero_copy.len(), 1, "Invalid number of arguments"); + let mut zero_copy = zero_copy[0].clone(); + let resource_table = isolate_state.resource_table.clone(); let op = async move { let mut resource_table_ = resource_table.borrow_mut(); @@ -92,7 +93,7 @@ pub fn receive_unix_packet( .ok_or_else(|| { OpError::bad_resource("Socket has been closed".to_string()) })?; - let (size, remote_addr) = resource.socket.recv_from(&mut buf).await?; + let (size, remote_addr) = resource.socket.recv_from(&mut zero_copy).await?; Ok(json!({ "size": size, "remoteAddr": { diff --git a/cli/ops/os.rs b/cli/ops/os.rs index 6c18015207c09f..36cd99577d1d0e 100644 --- a/cli/ops/os.rs +++ b/cli/ops/os.rs @@ -29,7 +29,7 @@ struct GetDirArgs { fn op_get_dir( state: &State, args: Value, - _zero_copy: Option, + _zero_copy: &mut [ZeroCopyBuf], ) -> Result { state.check_unstable("Deno.dir"); state.check_env()?; @@ -80,10 +80,10 @@ fn op_get_dir( fn op_exec_path( state: &State, _args: Value, - _zero_copy: Option, + _zero_copy: &mut [ZeroCopyBuf], ) -> Result { let current_exe = env::current_exe().unwrap(); - state.check_read(¤t_exe)?; + state.check_read_blind(¤t_exe, "exec_path")?; // Now apply URL parser to current exe to get fully resolved path, otherwise // we might get `./` and `../` bits in `exec_path` let exe_url = Url::from_file_path(current_exe).unwrap(); @@ -100,7 +100,7 @@ struct SetEnv { fn op_set_env( state: &State, args: Value, - _zero_copy: Option, + _zero_copy: &mut [ZeroCopyBuf], ) -> Result { let args: SetEnv = serde_json::from_value(args)?; state.check_env()?; @@ -111,7 +111,7 @@ fn op_set_env( fn op_env( state: &State, _args: Value, - _zero_copy: Option, + _zero_copy: &mut [ZeroCopyBuf], ) -> Result { state.check_env()?; let v = env::vars().collect::>(); @@ -126,7 +126,7 @@ struct GetEnv { fn op_get_env( state: &State, args: Value, - _zero_copy: Option, + _zero_copy: &mut [ZeroCopyBuf], ) -> Result { let args: GetEnv = serde_json::from_value(args)?; state.check_env()?; @@ -145,7 +145,7 @@ struct Exit { fn op_exit( _s: &State, args: Value, - _zero_copy: Option, + _zero_copy: &mut [ZeroCopyBuf], ) -> Result { let args: Exit = serde_json::from_value(args)?; std::process::exit(args.code) @@ -154,7 +154,7 @@ fn op_exit( fn op_loadavg( state: &State, _args: Value, - _zero_copy: Option, + _zero_copy: &mut [ZeroCopyBuf], ) -> Result { state.check_unstable("Deno.loadavg"); state.check_env()?; @@ -171,7 +171,7 @@ fn op_loadavg( fn op_hostname( state: &State, _args: Value, - _zero_copy: Option, + _zero_copy: &mut [ZeroCopyBuf], ) -> Result { state.check_unstable("Deno.hostname"); state.check_env()?; @@ -182,7 +182,7 @@ fn op_hostname( fn op_os_release( state: &State, _args: Value, - _zero_copy: Option, + _zero_copy: &mut [ZeroCopyBuf], ) -> Result { state.check_unstable("Deno.osRelease"); state.check_env()?; diff --git a/cli/ops/permissions.rs b/cli/ops/permissions.rs index 3ccff4065690e8..a4ee4120e7c056 100644 --- a/cli/ops/permissions.rs +++ b/cli/ops/permissions.rs @@ -1,6 +1,5 @@ // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. use super::dispatch_json::{Deserialize, JsonOp, Value}; -use crate::fs as deno_fs; use crate::op_error::OpError; use crate::state::State; use deno_core::CoreIsolate; @@ -29,26 +28,18 @@ struct PermissionArgs { path: Option, } -fn resolve_path(path: &str) -> String { - deno_fs::resolve_from_cwd(Path::new(path)) - .unwrap() - .to_str() - .unwrap() - .to_string() -} - pub fn op_query_permission( state: &State, args: Value, - _zero_copy: Option, + _zero_copy: &mut [ZeroCopyBuf], ) -> Result { let args: PermissionArgs = serde_json::from_value(args)?; let state = state.borrow(); - let resolved_path = args.path.as_deref().map(resolve_path); + let path = args.path.as_deref(); let perm = state.permissions.get_permission_state( &args.name, &args.url.as_deref(), - &resolved_path.as_deref().map(Path::new), + &path.as_deref().map(Path::new), )?; Ok(JsonOp::Sync(json!({ "state": perm.to_string() }))) } @@ -56,7 +47,7 @@ pub fn op_query_permission( pub fn op_revoke_permission( state: &State, args: Value, - _zero_copy: Option, + _zero_copy: &mut [ZeroCopyBuf], ) -> Result { let args: PermissionArgs = serde_json::from_value(args)?; let mut state = state.borrow_mut(); @@ -71,11 +62,11 @@ pub fn op_revoke_permission( "hrtime" => permissions.allow_hrtime.revoke(), _ => {} }; - let resolved_path = args.path.as_deref().map(resolve_path); + let path = args.path.as_deref(); let perm = permissions.get_permission_state( &args.name, &args.url.as_deref(), - &resolved_path.as_deref().map(Path::new), + &path.as_deref().map(Path::new), )?; Ok(JsonOp::Sync(json!({ "state": perm.to_string() }))) } @@ -83,20 +74,16 @@ pub fn op_revoke_permission( pub fn op_request_permission( state: &State, args: Value, - _zero_copy: Option, + _zero_copy: &mut [ZeroCopyBuf], ) -> Result { let args: PermissionArgs = serde_json::from_value(args)?; let mut state = state.borrow_mut(); let permissions = &mut state.permissions; - let resolved_path = args.path.as_deref().map(resolve_path); + let path = args.path.as_deref(); let perm = match args.name.as_ref() { "run" => Ok(permissions.request_run()), - "read" => { - Ok(permissions.request_read(&resolved_path.as_deref().map(Path::new))) - } - "write" => { - Ok(permissions.request_write(&resolved_path.as_deref().map(Path::new))) - } + "read" => Ok(permissions.request_read(&path.as_deref().map(Path::new))), + "write" => Ok(permissions.request_write(&path.as_deref().map(Path::new))), "net" => permissions.request_net(&args.url.as_deref()), "env" => Ok(permissions.request_env()), "plugin" => Ok(permissions.request_plugin()), diff --git a/cli/ops/plugin.rs b/cli/ops/plugin.rs index cabb3329db72cd..775178f1eda612 100644 --- a/cli/ops/plugin.rs +++ b/cli/ops/plugin.rs @@ -1,5 +1,4 @@ // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. -use crate::fs::resolve_from_cwd; use crate::op_error::OpError; use crate::ops::dispatch_json::Deserialize; use crate::ops::dispatch_json::JsonOp; @@ -8,13 +7,14 @@ use crate::ops::json_op; use crate::state::State; use deno_core::plugin_api; use deno_core::CoreIsolate; +use deno_core::CoreIsolateState; use deno_core::Op; use deno_core::OpAsyncFuture; use deno_core::OpId; use deno_core::ZeroCopyBuf; use dlopen::symbor::Library; use futures::prelude::*; -use std::path::Path; +use std::path::PathBuf; use std::pin::Pin; use std::rc::Rc; use std::task::Context; @@ -34,14 +34,14 @@ struct OpenPluginArgs { } pub fn op_open_plugin( - isolate: &mut CoreIsolate, + isolate_state: &mut CoreIsolateState, state: &State, args: Value, - _zero_copy: Option, + _zero_copy: &mut [ZeroCopyBuf], ) -> Result { state.check_unstable("Deno.openPlugin"); let args: OpenPluginArgs = serde_json::from_value(args).unwrap(); - let filename = resolve_from_cwd(Path::new(&args.filename))?; + let filename = PathBuf::from(&args.filename); state.check_plugin(&filename)?; @@ -51,7 +51,7 @@ pub fn op_open_plugin( .map_err(OpError::from)?; let plugin_resource = PluginResource::new(&plugin_lib); - let mut resource_table = isolate.resource_table.borrow_mut(); + let mut resource_table = isolate_state.resource_table.borrow_mut(); let rid = resource_table.add("plugin", Box::new(plugin_resource)); let plugin_resource = resource_table.get::(rid).unwrap(); @@ -63,7 +63,7 @@ pub fn op_open_plugin( .unwrap(); drop(resource_table); - let mut interface = PluginInterface::new(isolate, &plugin_lib); + let mut interface = PluginInterface::new(isolate_state, &plugin_lib); deno_plugin_init(&mut interface); Ok(JsonOp::Sync(json!(rid))) @@ -80,14 +80,17 @@ impl PluginResource { } struct PluginInterface<'a> { - isolate: &'a mut CoreIsolate, + isolate_state: &'a mut CoreIsolateState, plugin_lib: &'a Rc, } impl<'a> PluginInterface<'a> { - fn new(isolate: &'a mut CoreIsolate, plugin_lib: &'a Rc) -> Self { + fn new( + isolate_state: &'a mut CoreIsolateState, + plugin_lib: &'a Rc, + ) -> Self { Self { - isolate, + isolate_state, plugin_lib, } } @@ -105,10 +108,10 @@ impl<'a> plugin_api::Interface for PluginInterface<'a> { dispatch_op_fn: plugin_api::DispatchOpFn, ) -> OpId { let plugin_lib = self.plugin_lib.clone(); - self.isolate.op_registry.register( + self.isolate_state.op_registry.register( name, - move |isolate, control, zero_copy| { - let mut interface = PluginInterface::new(isolate, &plugin_lib); + move |isolate_state, control, zero_copy| { + let mut interface = PluginInterface::new(isolate_state, &plugin_lib); let op = dispatch_op_fn(&mut interface, control, zero_copy); match op { sync_op @ Op::Sync(..) => sync_op, diff --git a/cli/ops/process.rs b/cli/ops/process.rs index 125aa136b44da4..78d9313c0ceafd 100644 --- a/cli/ops/process.rs +++ b/cli/ops/process.rs @@ -5,6 +5,7 @@ use crate::op_error::OpError; use crate::signal::kill; use crate::state::State; use deno_core::CoreIsolate; +use deno_core::CoreIsolateState; use deno_core::ResourceTable; use deno_core::ZeroCopyBuf; use futures::future::poll_fn; @@ -60,15 +61,15 @@ struct ChildResource { } fn op_run( - isolate: &mut CoreIsolate, + isolate_state: &mut CoreIsolateState, state: &State, args: Value, - _zero_copy: Option, + _zero_copy: &mut [ZeroCopyBuf], ) -> Result { let run_args: RunArgs = serde_json::from_value(args)?; state.check_run()?; - let mut resource_table = isolate.resource_table.borrow_mut(); + let mut resource_table = isolate_state.resource_table.borrow_mut(); let args = run_args.cmd; let env = run_args.env; @@ -174,16 +175,16 @@ struct RunStatusArgs { } fn op_run_status( - isolate: &mut CoreIsolate, + isolate_state: &mut CoreIsolateState, state: &State, args: Value, - _zero_copy: Option, + _zero_copy: &mut [ZeroCopyBuf], ) -> Result { let args: RunStatusArgs = serde_json::from_value(args)?; let rid = args.rid as u32; state.check_run()?; - let resource_table = isolate.resource_table.clone(); + let resource_table = isolate_state.resource_table.clone(); let future = async move { let run_status = poll_fn(|cx| { @@ -227,7 +228,7 @@ struct KillArgs { fn op_kill( state: &State, args: Value, - _zero_copy: Option, + _zero_copy: &mut [ZeroCopyBuf], ) -> Result { state.check_unstable("Deno.kill"); state.check_run()?; diff --git a/cli/ops/random.rs b/cli/ops/random.rs index 874887cdb40863..b29c761b860562 100644 --- a/cli/ops/random.rs +++ b/cli/ops/random.rs @@ -17,15 +17,15 @@ pub fn init(i: &mut CoreIsolate, s: &State) { fn op_get_random_values( state: &State, _args: Value, - zero_copy: Option, + zero_copy: &mut [ZeroCopyBuf], ) -> Result { - assert!(zero_copy.is_some()); + assert_eq!(zero_copy.len(), 1); if let Some(ref mut seeded_rng) = state.borrow_mut().seeded_rng { - seeded_rng.fill(&mut zero_copy.unwrap()[..]); + seeded_rng.fill(&mut *zero_copy[0]); } else { let mut rng = thread_rng(); - rng.fill(&mut zero_copy.unwrap()[..]); + rng.fill(&mut *zero_copy[0]); } Ok(JsonOp::Sync(json!({}))) diff --git a/cli/ops/repl.rs b/cli/ops/repl.rs index 7dc0d0263da07f..b8fd7ab8b3c91a 100644 --- a/cli/ops/repl.rs +++ b/cli/ops/repl.rs @@ -5,6 +5,7 @@ use crate::repl; use crate::repl::Repl; use crate::state::State; use deno_core::CoreIsolate; +use deno_core::CoreIsolateState; use deno_core::ZeroCopyBuf; use std::sync::Arc; use std::sync::Mutex; @@ -23,10 +24,10 @@ struct ReplStartArgs { } fn op_repl_start( - isolate: &mut CoreIsolate, + isolate_state: &mut CoreIsolateState, state: &State, args: Value, - _zero_copy: Option, + _zero_copy: &mut [ZeroCopyBuf], ) -> Result { let args: ReplStartArgs = serde_json::from_value(args)?; debug!("op_repl_start {}", args.history_file); @@ -34,7 +35,7 @@ fn op_repl_start( repl::history_path(&state.borrow().global_state.dir, &args.history_file); let repl = repl::Repl::new(history_path); let resource = ReplResource(Arc::new(Mutex::new(repl))); - let mut resource_table = isolate.resource_table.borrow_mut(); + let mut resource_table = isolate_state.resource_table.borrow_mut(); let rid = resource_table.add("repl", Box::new(resource)); Ok(JsonOp::Sync(json!(rid))) } @@ -46,16 +47,16 @@ struct ReplReadlineArgs { } fn op_repl_readline( - isolate: &mut CoreIsolate, + isolate_state: &mut CoreIsolateState, _state: &State, args: Value, - _zero_copy: Option, + _zero_copy: &mut [ZeroCopyBuf], ) -> Result { let args: ReplReadlineArgs = serde_json::from_value(args)?; let rid = args.rid as u32; let prompt = args.prompt; debug!("op_repl_readline {} {}", rid, prompt); - let resource_table = isolate.resource_table.borrow(); + let resource_table = isolate_state.resource_table.borrow(); let resource = resource_table .get::(rid) .ok_or_else(OpError::bad_resource_id)?; diff --git a/cli/ops/resources.rs b/cli/ops/resources.rs index 1aa8dd4dc2cce7..a66a661700ee90 100644 --- a/cli/ops/resources.rs +++ b/cli/ops/resources.rs @@ -3,6 +3,7 @@ use super::dispatch_json::{Deserialize, JsonOp, Value}; use crate::op_error::OpError; use crate::state::State; use deno_core::CoreIsolate; +use deno_core::CoreIsolateState; use deno_core::ZeroCopyBuf; pub fn init(i: &mut CoreIsolate, s: &State) { @@ -11,28 +12,28 @@ pub fn init(i: &mut CoreIsolate, s: &State) { } fn op_resources( - isolate: &mut CoreIsolate, + isolate_state: &mut CoreIsolateState, _state: &State, _args: Value, - _zero_copy: Option, + _zero_copy: &mut [ZeroCopyBuf], ) -> Result { - let serialized_resources = isolate.resource_table.borrow().entries(); + let serialized_resources = isolate_state.resource_table.borrow().entries(); Ok(JsonOp::Sync(json!(serialized_resources))) } /// op_close removes a resource from the resource table. fn op_close( - isolate: &mut CoreIsolate, + isolate_state: &mut CoreIsolateState, _state: &State, args: Value, - _zero_copy: Option, + _zero_copy: &mut [ZeroCopyBuf], ) -> Result { #[derive(Deserialize)] struct CloseArgs { rid: i32, } let args: CloseArgs = serde_json::from_value(args)?; - let mut resource_table = isolate.resource_table.borrow_mut(); + let mut resource_table = isolate_state.resource_table.borrow_mut(); resource_table .close(args.rid as u32) .ok_or_else(OpError::bad_resource_id)?; diff --git a/cli/ops/runtime.rs b/cli/ops/runtime.rs index e96a669d048a42..a85ce4011d5bc2 100644 --- a/cli/ops/runtime.rs +++ b/cli/ops/runtime.rs @@ -17,7 +17,7 @@ pub fn init(i: &mut CoreIsolate, s: &State) { fn op_start( state: &State, _args: Value, - _zero_copy: Option, + _zero_copy: &mut [ZeroCopyBuf], ) -> Result { let state = state.borrow(); let gs = &state.global_state; @@ -42,7 +42,7 @@ fn op_start( fn op_metrics( state: &State, _args: Value, - _zero_copy: Option, + _zero_copy: &mut [ZeroCopyBuf], ) -> Result { let state = state.borrow(); let m = &state.metrics; diff --git a/cli/ops/runtime_compiler.rs b/cli/ops/runtime_compiler.rs index f3b741861a8668..97102ef8196492 100644 --- a/cli/ops/runtime_compiler.rs +++ b/cli/ops/runtime_compiler.rs @@ -26,7 +26,7 @@ struct CompileArgs { fn op_compile( state: &State, args: Value, - _zero_copy: Option, + _zero_copy: &mut [ZeroCopyBuf], ) -> Result { state.check_unstable("Deno.compile"); let args: CompileArgs = serde_json::from_value(args)?; @@ -57,7 +57,7 @@ struct TranspileArgs { fn op_transpile( state: &State, args: Value, - _zero_copy: Option, + _zero_copy: &mut [ZeroCopyBuf], ) -> Result { state.check_unstable("Deno.transpile"); let args: TranspileArgs = serde_json::from_value(args)?; diff --git a/cli/ops/signal.rs b/cli/ops/signal.rs index ef652bc67153cd..6457ac42e484e7 100644 --- a/cli/ops/signal.rs +++ b/cli/ops/signal.rs @@ -3,6 +3,7 @@ use super::dispatch_json::{JsonOp, Value}; use crate::op_error::OpError; use crate::state::State; use deno_core::CoreIsolate; +use deno_core::CoreIsolateState; use deno_core::ZeroCopyBuf; #[cfg(unix)] @@ -39,14 +40,14 @@ struct SignalArgs { #[cfg(unix)] fn op_signal_bind( - isolate: &mut CoreIsolate, + isolate_state: &mut CoreIsolateState, state: &State, args: Value, - _zero_copy: Option, + _zero_copy: &mut [ZeroCopyBuf], ) -> Result { state.check_unstable("Deno.signal"); let args: BindSignalArgs = serde_json::from_value(args)?; - let mut resource_table = isolate.resource_table.borrow_mut(); + let mut resource_table = isolate_state.resource_table.borrow_mut(); let rid = resource_table.add( "signal", Box::new(SignalStreamResource( @@ -61,15 +62,15 @@ fn op_signal_bind( #[cfg(unix)] fn op_signal_poll( - isolate: &mut CoreIsolate, + isolate_state: &mut CoreIsolateState, state: &State, args: Value, - _zero_copy: Option, + _zero_copy: &mut [ZeroCopyBuf], ) -> Result { state.check_unstable("Deno.signal"); let args: SignalArgs = serde_json::from_value(args)?; let rid = args.rid as u32; - let resource_table = isolate.resource_table.clone(); + let resource_table = isolate_state.resource_table.clone(); let future = poll_fn(move |cx| { let mut resource_table = resource_table.borrow_mut(); @@ -88,15 +89,15 @@ fn op_signal_poll( #[cfg(unix)] pub fn op_signal_unbind( - isolate: &mut CoreIsolate, + isolate_state: &mut CoreIsolateState, state: &State, args: Value, - _zero_copy: Option, + _zero_copy: &mut [ZeroCopyBuf], ) -> Result { state.check_unstable("Deno.signal"); let args: SignalArgs = serde_json::from_value(args)?; let rid = args.rid as u32; - let mut resource_table = isolate.resource_table.borrow_mut(); + let mut resource_table = isolate_state.resource_table.borrow_mut(); let resource = resource_table.get::(rid); if let Some(signal) = resource { if let Some(waker) = &signal.1 { @@ -113,30 +114,30 @@ pub fn op_signal_unbind( #[cfg(not(unix))] pub fn op_signal_bind( - _isolate: &mut CoreIsolate, + _isolate_state: &mut CoreIsolateState, _state: &State, _args: Value, - _zero_copy: Option, + _zero_copy: &mut [ZeroCopyBuf], ) -> Result { unimplemented!(); } #[cfg(not(unix))] fn op_signal_unbind( - _isolate: &mut CoreIsolate, + _isolate_state: &mut CoreIsolateState, _state: &State, _args: Value, - _zero_copy: Option, + _zero_copy: &mut [ZeroCopyBuf], ) -> Result { unimplemented!(); } #[cfg(not(unix))] fn op_signal_poll( - _isolate: &mut CoreIsolate, + _isolate_state: &mut CoreIsolateState, _state: &State, _args: Value, - _zero_copy: Option, + _zero_copy: &mut [ZeroCopyBuf], ) -> Result { unimplemented!(); } diff --git a/cli/ops/timers.rs b/cli/ops/timers.rs index e5bc461d03e887..044c5ea4afa841 100644 --- a/cli/ops/timers.rs +++ b/cli/ops/timers.rs @@ -20,7 +20,7 @@ pub fn init(i: &mut CoreIsolate, s: &State) { fn op_global_timer_stop( state: &State, _args: Value, - _zero_copy: Option, + _zero_copy: &mut [ZeroCopyBuf], ) -> Result { let mut state = state.borrow_mut(); state.global_timer.cancel(); @@ -35,7 +35,7 @@ struct GlobalTimerArgs { fn op_global_timer( state: &State, args: Value, - _zero_copy: Option, + _zero_copy: &mut [ZeroCopyBuf], ) -> Result { let args: GlobalTimerArgs = serde_json::from_value(args)?; let val = args.timeout; @@ -57,7 +57,7 @@ fn op_global_timer( fn op_now( state: &State, _args: Value, - _zero_copy: Option, + _zero_copy: &mut [ZeroCopyBuf], ) -> Result { let state = state.borrow(); let seconds = state.start_time.elapsed().as_secs(); diff --git a/cli/ops/tls.rs b/cli/ops/tls.rs index 5cbdff3007e33f..3e22c71ea67253 100644 --- a/cli/ops/tls.rs +++ b/cli/ops/tls.rs @@ -5,6 +5,7 @@ use crate::op_error::OpError; use crate::resolve_addr::resolve_addr; use crate::state::State; use deno_core::CoreIsolate; +use deno_core::CoreIsolateState; use deno_core::ZeroCopyBuf; use futures::future::poll_fn; use futures::future::FutureExt; @@ -53,16 +54,16 @@ struct StartTLSArgs { } pub fn op_start_tls( - isolate: &mut CoreIsolate, + isolate_state: &mut CoreIsolateState, state: &State, args: Value, - _zero_copy: Option, + _zero_copy: &mut [ZeroCopyBuf], ) -> Result { state.check_unstable("Deno.startTls"); let args: StartTLSArgs = serde_json::from_value(args)?; let rid = args.rid as u32; let cert_file = args.cert_file.clone(); - let resource_table = isolate.resource_table.clone(); + let resource_table = isolate_state.resource_table.clone(); let mut domain = args.hostname; if domain.is_empty() { @@ -132,14 +133,14 @@ pub fn op_start_tls( } pub fn op_connect_tls( - isolate: &mut CoreIsolate, + isolate_state: &mut CoreIsolateState, state: &State, args: Value, - _zero_copy: Option, + _zero_copy: &mut [ZeroCopyBuf], ) -> Result { let args: ConnectTLSArgs = serde_json::from_value(args)?; let cert_file = args.cert_file.clone(); - let resource_table = isolate.resource_table.clone(); + let resource_table = isolate_state.resource_table.clone(); state.check_net(&args.hostname, args.port)?; if let Some(path) = cert_file.clone() { state.check_read(Path::new(&path))?; @@ -306,10 +307,10 @@ struct ListenTlsArgs { } fn op_listen_tls( - isolate: &mut CoreIsolate, + isolate_state: &mut CoreIsolateState, state: &State, args: Value, - _zero_copy: Option, + _zero_copy: &mut [ZeroCopyBuf], ) -> Result { let args: ListenTlsArgs = serde_json::from_value(args)?; assert_eq!(args.transport, "tcp"); @@ -337,7 +338,7 @@ fn op_listen_tls( local_addr, }; - let mut resource_table = isolate.resource_table.borrow_mut(); + let mut resource_table = isolate_state.resource_table.borrow_mut(); let rid = resource_table.add("tlsListener", Box::new(tls_listener_resource)); Ok(JsonOp::Sync(json!({ @@ -356,14 +357,14 @@ struct AcceptTlsArgs { } fn op_accept_tls( - isolate: &mut CoreIsolate, + isolate_state: &mut CoreIsolateState, _state: &State, args: Value, - _zero_copy: Option, + _zero_copy: &mut [ZeroCopyBuf], ) -> Result { let args: AcceptTlsArgs = serde_json::from_value(args)?; let rid = args.rid as u32; - let resource_table = isolate.resource_table.clone(); + let resource_table = isolate_state.resource_table.clone(); let op = async move { let accept_fut = poll_fn(|cx| { let mut resource_table = resource_table.borrow_mut(); diff --git a/cli/ops/tty.rs b/cli/ops/tty.rs index cf7f62ed889a50..ee357ed68ab2c2 100644 --- a/cli/ops/tty.rs +++ b/cli/ops/tty.rs @@ -4,6 +4,7 @@ use super::io::{StreamResource, StreamResourceHolder}; use crate::op_error::OpError; use crate::state::State; use deno_core::CoreIsolate; +use deno_core::CoreIsolateState; use deno_core::ZeroCopyBuf; #[cfg(unix)] use nix::sys::termios; @@ -46,10 +47,10 @@ struct SetRawArgs { } pub fn op_set_raw( - isolate: &mut CoreIsolate, + isolate_state: &mut CoreIsolateState, state: &State, args: Value, - _zero_copy: Option, + _zero_copy: &mut [ZeroCopyBuf], ) -> Result { state.check_unstable("Deno.setRaw"); let args: SetRawArgs = serde_json::from_value(args)?; @@ -67,7 +68,7 @@ pub fn op_set_raw( use winapi::shared::minwindef::FALSE; use winapi::um::{consoleapi, handleapi}; - let mut resource_table = isolate.resource_table.borrow_mut(); + let mut resource_table = isolate_state.resource_table.borrow_mut(); let resource_holder = resource_table.get_mut::(rid); if resource_holder.is_none() { return Err(OpError::bad_resource_id()); @@ -133,7 +134,7 @@ pub fn op_set_raw( { use std::os::unix::io::AsRawFd; - let mut resource_table = isolate.resource_table.borrow_mut(); + let mut resource_table = isolate_state.resource_table.borrow_mut(); let resource_holder = resource_table.get_mut::(rid); if resource_holder.is_none() { return Err(OpError::bad_resource_id()); @@ -215,15 +216,15 @@ struct IsattyArgs { } pub fn op_isatty( - isolate: &mut CoreIsolate, + isolate_state: &mut CoreIsolateState, _state: &State, args: Value, - _zero_copy: Option, + _zero_copy: &mut [ZeroCopyBuf], ) -> Result { let args: IsattyArgs = serde_json::from_value(args)?; let rid = args.rid; - let mut resource_table = isolate.resource_table.borrow_mut(); + let mut resource_table = isolate_state.resource_table.borrow_mut(); let isatty: bool = std_file_resource(&mut resource_table, rid as u32, move |r| match r { Ok(std_file) => { diff --git a/cli/ops/web_worker.rs b/cli/ops/web_worker.rs index e95eb8fe110fc0..553278b0741e1c 100644 --- a/cli/ops/web_worker.rs +++ b/cli/ops/web_worker.rs @@ -6,6 +6,7 @@ use crate::state::State; use crate::web_worker::WebWorkerHandle; use crate::worker::WorkerEvent; use deno_core::CoreIsolate; +use deno_core::CoreIsolateState; use deno_core::ZeroCopyBuf; use futures::channel::mpsc; use std::convert::From; @@ -13,17 +14,21 @@ use std::convert::From; pub fn web_worker_op( sender: mpsc::Sender, dispatcher: D, -) -> impl Fn(&mut CoreIsolate, Value, Option) -> Result +) -> impl Fn( + &mut CoreIsolateState, + Value, + &mut [ZeroCopyBuf], +) -> Result where D: Fn( &mpsc::Sender, Value, - Option, + &mut [ZeroCopyBuf], ) -> Result, { - move |_isolate: &mut CoreIsolate, + move |_isolate_state: &mut CoreIsolateState, args: Value, - zero_copy: Option| + zero_copy: &mut [ZeroCopyBuf]| -> Result { dispatcher(&sender, args, zero_copy) } } @@ -31,18 +36,22 @@ pub fn web_worker_op2( handle: WebWorkerHandle, sender: mpsc::Sender, dispatcher: D, -) -> impl Fn(&mut CoreIsolate, Value, Option) -> Result +) -> impl Fn( + &mut CoreIsolateState, + Value, + &mut [ZeroCopyBuf], +) -> Result where D: Fn( WebWorkerHandle, &mpsc::Sender, Value, - Option, + &mut [ZeroCopyBuf], ) -> Result, { - move |_isolate: &mut CoreIsolate, + move |_isolate_state: &mut CoreIsolateState, args: Value, - zero_copy: Option| + zero_copy: &mut [ZeroCopyBuf]| -> Result { dispatcher(handle.clone(), &sender, args, zero_copy) } @@ -75,9 +84,10 @@ pub fn init( fn op_worker_post_message( sender: &mpsc::Sender, _args: Value, - data: Option, + data: &mut [ZeroCopyBuf], ) -> Result { - let d = Vec::from(data.unwrap().as_ref()).into_boxed_slice(); + assert_eq!(data.len(), 1, "Invalid number of arguments"); + let d = Vec::from(&*data[0]).into_boxed_slice(); let mut sender = sender.clone(); sender .try_send(WorkerEvent::Message(d)) @@ -90,7 +100,7 @@ fn op_worker_close( handle: WebWorkerHandle, sender: &mpsc::Sender, _args: Value, - _data: Option, + _data: &mut [ZeroCopyBuf], ) -> Result { let mut sender = sender.clone(); // Notify parent that we're finished diff --git a/cli/ops/worker_host.rs b/cli/ops/worker_host.rs index d1b4bb80f1f1c1..0ed700431afaf0 100644 --- a/cli/ops/worker_host.rs +++ b/cli/ops/worker_host.rs @@ -54,7 +54,9 @@ fn create_web_worker( ); if has_deno_namespace { - let mut resource_table = worker.resource_table.borrow_mut(); + let state_rc = CoreIsolate::state(&worker.isolate); + let state = state_rc.borrow(); + let mut resource_table = state.resource_table.borrow_mut(); let (stdin, stdout, stderr) = get_stdio(); resource_table.add("stdin", Box::new(stdin)); resource_table.add("stdout", Box::new(stdout)); @@ -172,7 +174,7 @@ struct CreateWorkerArgs { fn op_create_worker( state: &State, args: Value, - _data: Option, + _data: &mut [ZeroCopyBuf], ) -> Result { let args: CreateWorkerArgs = serde_json::from_value(args)?; @@ -228,7 +230,7 @@ struct WorkerArgs { fn op_host_terminate_worker( state: &State, args: Value, - _data: Option, + _data: &mut [ZeroCopyBuf], ) -> Result { let args: WorkerArgs = serde_json::from_value(args)?; let id = args.id as u32; @@ -294,7 +296,7 @@ fn serialize_worker_event(event: WorkerEvent) -> Value { fn op_host_get_message( state: &State, args: Value, - _data: Option, + _data: &mut [ZeroCopyBuf], ) -> Result { let args: WorkerArgs = serde_json::from_value(args)?; let id = args.id as u32; @@ -343,11 +345,12 @@ fn op_host_get_message( fn op_host_post_message( state: &State, args: Value, - data: Option, + data: &mut [ZeroCopyBuf], ) -> Result { + assert_eq!(data.len(), 1, "Invalid number of arguments"); let args: WorkerArgs = serde_json::from_value(args)?; let id = args.id as u32; - let msg = Vec::from(data.unwrap().as_ref()).into_boxed_slice(); + let msg = Vec::from(&*data[0]).into_boxed_slice(); debug!("post message to worker {}", id); let state = state.borrow(); diff --git a/cli/permissions.rs b/cli/permissions.rs index bc4d0844e0ec63..298259f6bf8072 100644 --- a/cli/permissions.rs +++ b/cli/permissions.rs @@ -1,8 +1,12 @@ // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. use crate::colors; use crate::flags::Flags; +use crate::fs::resolve_from_cwd; use crate::op_error::OpError; +use serde::de; +use serde::Deserialize; use std::collections::HashSet; +use std::env::current_dir; use std::fmt; #[cfg(not(test))] use std::io; @@ -96,35 +100,71 @@ impl Default for PermissionState { } } -#[derive(Clone, Debug, Default)] +struct BoolPermVisitor; + +fn deserialize_permission_state<'de, D>( + d: D, +) -> Result +where + D: de::Deserializer<'de>, +{ + impl<'de> de::Visitor<'de> for BoolPermVisitor { + type Value = PermissionState; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a boolean value") + } + + fn visit_bool(self, value: bool) -> Result + where + E: de::Error, + { + if value { + Ok(PermissionState::Allow) + } else { + Ok(PermissionState::Deny) + } + } + } + d.deserialize_bool(BoolPermVisitor) +} + +#[derive(Clone, Debug, Default, Deserialize, PartialEq)] pub struct Permissions { // Keep in sync with cli/js/permissions.ts + #[serde(deserialize_with = "deserialize_permission_state")] pub allow_read: PermissionState, pub read_whitelist: HashSet, + #[serde(deserialize_with = "deserialize_permission_state")] pub allow_write: PermissionState, pub write_whitelist: HashSet, + #[serde(deserialize_with = "deserialize_permission_state")] pub allow_net: PermissionState, pub net_whitelist: HashSet, + #[serde(deserialize_with = "deserialize_permission_state")] pub allow_env: PermissionState, + #[serde(deserialize_with = "deserialize_permission_state")] pub allow_run: PermissionState, + #[serde(deserialize_with = "deserialize_permission_state")] pub allow_plugin: PermissionState, + #[serde(deserialize_with = "deserialize_permission_state")] pub allow_hrtime: PermissionState, } +fn resolve_fs_whitelist(whitelist: &[PathBuf]) -> HashSet { + whitelist + .iter() + .map(|raw_path| resolve_from_cwd(Path::new(&raw_path)).unwrap()) + .collect() +} + impl Permissions { pub fn from_flags(flags: &Flags) -> Self { - // assert each whitelist path is absolute, since the cwd may change. - for path in &flags.read_whitelist { - assert!(path.has_root()); - } - for path in &flags.write_whitelist { - assert!(path.has_root()); - } Self { allow_read: PermissionState::from(flags.allow_read), - read_whitelist: flags.read_whitelist.iter().cloned().collect(), + read_whitelist: resolve_fs_whitelist(&flags.read_whitelist), allow_write: PermissionState::from(flags.allow_write), - write_whitelist: flags.write_whitelist.iter().cloned().collect(), + write_whitelist: resolve_fs_whitelist(&flags.write_whitelist), allow_net: PermissionState::from(flags.allow_net), net_whitelist: flags.net_whitelist.iter().cloned().collect(), allow_env: PermissionState::from(flags.allow_env), @@ -134,6 +174,24 @@ impl Permissions { } } + /// Arbitrary helper. Resolves the path from CWD, and also gets a path that + /// can be displayed without leaking the CWD when not allowed. + fn resolved_and_display_path(&self, path: &Path) -> (PathBuf, PathBuf) { + let resolved_path = resolve_from_cwd(path).unwrap(); + let display_path = if path.is_absolute() { + path.to_path_buf() + } else { + match self + .get_state_read(&Some(¤t_dir().unwrap())) + .check("", "") + { + Ok(_) => resolved_path.clone(), + Err(_) => path.to_path_buf(), + } + }; + (resolved_path, display_path) + } + pub fn allow_all() -> Self { Self { allow_read: PermissionState::from(true), @@ -161,12 +219,26 @@ impl Permissions { } pub fn check_read(&self, path: &Path) -> Result<(), OpError> { - self.get_state_read(&Some(path)).check( - &format!("read access to \"{}\"", path.display()), + let (resolved_path, display_path) = self.resolved_and_display_path(path); + self.get_state_read(&Some(&resolved_path)).check( + &format!("read access to \"{}\"", display_path.display()), "--allow-read", ) } + /// As `check_read()`, but permission error messages will anonymize the path + /// by replacing it with the given `display`. + pub fn check_read_blind( + &self, + path: &Path, + display: &str, + ) -> Result<(), OpError> { + let resolved_path = resolve_from_cwd(path).unwrap(); + self + .get_state_read(&Some(&resolved_path)) + .check(&format!("read access to <{}>", display), "--allow-read") + } + fn get_state_write(&self, path: &Option<&Path>) -> PermissionState { if path.map_or(false, |f| check_path_white_list(f, &self.write_whitelist)) { return PermissionState::Allow; @@ -175,8 +247,9 @@ impl Permissions { } pub fn check_write(&self, path: &Path) -> Result<(), OpError> { - self.get_state_write(&Some(path)).check( - &format!("write access to \"{}\"", path.display()), + let (resolved_path, display_path) = self.resolved_and_display_path(path); + self.get_state_write(&Some(&resolved_path)).check( + &format!("write access to \"{}\"", display_path.display()), "--allow-write", ) } @@ -226,8 +299,9 @@ impl Permissions { } pub fn check_plugin(&self, path: &Path) -> Result<(), OpError> { + let (_, display_path) = self.resolved_and_display_path(path); self.allow_plugin.check( - &format!("access to open a plugin: {}", path.display()), + &format!("access to open a plugin: {}", display_path.display()), "--allow-plugin", ) } @@ -239,26 +313,34 @@ impl Permissions { } pub fn request_read(&mut self, path: &Option<&Path>) -> PermissionState { - if path.map_or(false, |f| check_path_white_list(f, &self.read_whitelist)) { - return PermissionState::Allow; + let paths = path.map(|p| self.resolved_and_display_path(p)); + if let Some((p, _)) = paths.as_ref() { + if check_path_white_list(&p, &self.read_whitelist) { + return PermissionState::Allow; + } }; - self.allow_read.request(&match path { + self.allow_read.request(&match paths { None => "Deno requests read access".to_string(), - Some(path) => { - format!("Deno requests read access to \"{}\"", path.display()) - } + Some((_, display_path)) => format!( + "Deno requests read access to \"{}\"", + display_path.display() + ), }) } pub fn request_write(&mut self, path: &Option<&Path>) -> PermissionState { - if path.map_or(false, |f| check_path_white_list(f, &self.write_whitelist)) { - return PermissionState::Allow; + let paths = path.map(|p| self.resolved_and_display_path(p)); + if let Some((p, _)) = paths.as_ref() { + if check_path_white_list(&p, &self.write_whitelist) { + return PermissionState::Allow; + } }; - self.allow_write.request(&match path { + self.allow_write.request(&match paths { None => "Deno requests write access".to_string(), - Some(path) => { - format!("Deno requests write access to \"{}\"", path.display()) - } + Some((_, display_path)) => format!( + "Deno requests write access to \"{}\"", + display_path.display() + ), }) } @@ -297,10 +379,12 @@ impl Permissions { url: &Option<&str>, path: &Option<&Path>, ) -> Result { + let path = path.map(|p| resolve_from_cwd(p).unwrap()); + let path = path.as_deref(); match name { "run" => Ok(self.allow_run), - "read" => Ok(self.get_state_read(path)), - "write" => Ok(self.get_state_write(path)), + "read" => Ok(self.get_state_read(&path)), + "write" => Ok(self.get_state_write(&path)), "net" => self.get_state_net_url(url), "env" => Ok(self.allow_env), "plugin" => Ok(self.allow_plugin), @@ -448,6 +532,14 @@ mod tests { assert!(perms.check_read(Path::new("/b/c/sub/path")).is_ok()); assert!(perms.check_write(Path::new("/b/c/sub/path")).is_ok()); + // Sub path within /b/c, needs normalizing + assert!(perms + .check_read(Path::new("/b/c/sub/path/../path/.")) + .is_ok()); + assert!(perms + .check_write(Path::new("/b/c/sub/path/../path/.")) + .is_ok()); + // Inside of /b but outside of /b/c assert!(perms.check_read(Path::new("/b/e")).is_err()); assert!(perms.check_write(Path::new("/b/e")).is_err()); @@ -735,4 +827,37 @@ mod tests { assert_eq!(perms1.request_hrtime(), PermissionState::Deny); drop(guard); } + + #[test] + fn test_deserialize_perms() { + let json_perms = r#" + { + "allow_read": true, + "read_whitelist": [], + "allow_write": true, + "write_whitelist": [], + "allow_net": true, + "net_whitelist": [], + "allow_env": true, + "allow_run": true, + "allow_plugin": true, + "allow_hrtime": true + } + "#; + let perms0 = Permissions { + allow_read: PermissionState::Allow, + allow_write: PermissionState::Allow, + allow_net: PermissionState::Allow, + allow_hrtime: PermissionState::Allow, + allow_env: PermissionState::Allow, + allow_plugin: PermissionState::Allow, + allow_run: PermissionState::Allow, + read_whitelist: HashSet::new(), + write_whitelist: HashSet::new(), + net_whitelist: HashSet::new(), + }; + let deserialized_perms: Permissions = + serde_json::from_str(json_perms).unwrap(); + assert_eq!(perms0, deserialized_perms); + } } diff --git a/cli/source_maps.rs b/cli/source_maps.rs index 90780e042ed702..d87147dbe61a9d 100644 --- a/cli/source_maps.rs +++ b/cli/source_maps.rs @@ -18,17 +18,6 @@ pub trait SourceMapGetter { /// find a SourceMap. pub type CachedMaps = HashMap>; -// The bundle does not get built for 'cargo check', so we don't embed the -// bundle source map. The built in source map is the source map for the main -// JavaScript bundle which is then used to create the snapshot. Runtime stack -// traces can contain positions within the bundle which we will map to the -// original Deno TypeScript code. -#[cfg(feature = "check-only")] -fn builtin_source_map(_: &str) -> Option> { - None -} - -#[cfg(not(feature = "check-only"))] fn builtin_source_map(file_name: &str) -> Option> { if file_name.ends_with("CLI_SNAPSHOT.js") { Some(crate::js::CLI_SNAPSHOT_MAP.to_vec()) diff --git a/cli/startup_data.rs b/cli/startup_data.rs index 517165d110c5f9..86bae2f47153b9 100644 --- a/cli/startup_data.rs +++ b/cli/startup_data.rs @@ -1,7 +1,4 @@ // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. -#[cfg(feature = "no-snapshot-init")] -use deno_core::Script; - use crate::js::CLI_SNAPSHOT; use crate::js::COMPILER_SNAPSHOT; use deno_core::Snapshot; @@ -10,13 +7,9 @@ use deno_core::StartupData; #[cfg(feature = "no-snapshot-init")] pub fn deno_isolate_init() -> StartupData<'static> { debug!("Deno isolate init without snapshots."); - #[cfg(not(feature = "check-only"))] let source = include_str!(concat!(env!("GN_OUT_DIR"), "/gen/cli/bundle/main.js")); - #[cfg(feature = "check-only")] - let source = ""; - - StartupData::Script(Script { + StartupData::Script(deno_core::Script { filename: "gen/cli/bundle/main.js", source, }) @@ -25,24 +18,16 @@ pub fn deno_isolate_init() -> StartupData<'static> { #[cfg(not(feature = "no-snapshot-init"))] pub fn deno_isolate_init() -> StartupData<'static> { debug!("Deno isolate init with snapshots."); - #[cfg(not(feature = "check-only"))] let data = CLI_SNAPSHOT; - #[cfg(feature = "check-only")] - let data = b""; - StartupData::Snapshot(Snapshot::Static(data)) } #[cfg(feature = "no-snapshot-init")] pub fn compiler_isolate_init() -> StartupData<'static> { debug!("Compiler isolate init without snapshots."); - #[cfg(not(feature = "check-only"))] let source = include_str!(concat!(env!("GN_OUT_DIR"), "/gen/cli/bundle/compiler.js")); - #[cfg(feature = "check-only")] - let source = ""; - - StartupData::Script(Script { + StartupData::Script(deno_core::Script { filename: "gen/cli/bundle/compiler.js", source, }) @@ -51,10 +36,6 @@ pub fn compiler_isolate_init() -> StartupData<'static> { #[cfg(not(feature = "no-snapshot-init"))] pub fn compiler_isolate_init() -> StartupData<'static> { debug!("Deno isolate init with snapshots."); - #[cfg(not(feature = "check-only"))] let data = COMPILER_SNAPSHOT; - #[cfg(feature = "check-only")] - let data = b""; - StartupData::Snapshot(Snapshot::Static(data)) } diff --git a/cli/state.rs b/cli/state.rs index baac89216ff87c..c94f927880224f 100644 --- a/cli/state.rs +++ b/cli/state.rs @@ -65,9 +65,9 @@ impl State { pub fn stateful_json_op( &self, dispatcher: D, - ) -> impl Fn(&mut deno_core::CoreIsolate, &[u8], Option) -> Op + ) -> impl Fn(&mut deno_core::CoreIsolateState, &[u8], &mut [ZeroCopyBuf]) -> Op where - D: Fn(&State, Value, Option) -> Result, + D: Fn(&State, Value, &mut [ZeroCopyBuf]) -> Result, { use crate::ops::json_op; self.core_op(json_op(self.stateful_op(dispatcher))) @@ -76,13 +76,13 @@ impl State { pub fn stateful_json_op2( &self, dispatcher: D, - ) -> impl Fn(&mut deno_core::CoreIsolate, &[u8], Option) -> Op + ) -> impl Fn(&mut deno_core::CoreIsolateState, &[u8], &mut [ZeroCopyBuf]) -> Op where D: Fn( - &mut deno_core::CoreIsolate, + &mut deno_core::CoreIsolateState, &State, Value, - Option, + &mut [ZeroCopyBuf], ) -> Result, { use crate::ops::json_op; @@ -95,21 +95,21 @@ impl State { pub fn core_op( &self, dispatcher: D, - ) -> impl Fn(&mut deno_core::CoreIsolate, &[u8], Option) -> Op + ) -> impl Fn(&mut deno_core::CoreIsolateState, &[u8], &mut [ZeroCopyBuf]) -> Op where - D: Fn(&mut deno_core::CoreIsolate, &[u8], Option) -> Op, + D: Fn(&mut deno_core::CoreIsolateState, &[u8], &mut [ZeroCopyBuf]) -> Op, { let state = self.clone(); - move |isolate: &mut deno_core::CoreIsolate, + move |isolate_state: &mut deno_core::CoreIsolateState, control: &[u8], - zero_copy: Option| + zero_copy: &mut [ZeroCopyBuf]| -> Op { let bytes_sent_control = control.len() as u64; let bytes_sent_zero_copy = - zero_copy.as_ref().map(|b| b.len()).unwrap_or(0) as u64; + zero_copy.iter().map(|b| b.len()).sum::() as u64; - let op = dispatcher(isolate, control, zero_copy); + let op = dispatcher(isolate_state, control, zero_copy); match op { Op::Sync(buf) => { @@ -155,24 +155,24 @@ impl State { pub fn stateful_minimal_op2( &self, dispatcher: D, - ) -> impl Fn(&mut deno_core::CoreIsolate, &[u8], Option) -> Op + ) -> impl Fn(&mut deno_core::CoreIsolateState, &[u8], &mut [ZeroCopyBuf]) -> Op where D: Fn( - &mut deno_core::CoreIsolate, + &mut deno_core::CoreIsolateState, &State, bool, i32, - Option, + &mut [ZeroCopyBuf], ) -> MinimalOp, { let state = self.clone(); self.core_op(crate::ops::minimal_op( - move |isolate: &mut deno_core::CoreIsolate, + move |isolate_state: &mut deno_core::CoreIsolateState, is_sync: bool, rid: i32, - zero_copy: Option| + zero_copy: &mut [ZeroCopyBuf]| -> MinimalOp { - dispatcher(isolate, &state, is_sync, rid, zero_copy) + dispatcher(isolate_state, &state, is_sync, rid, zero_copy) }, )) } @@ -186,17 +186,17 @@ impl State { &self, dispatcher: D, ) -> impl Fn( - &mut deno_core::CoreIsolate, + &mut deno_core::CoreIsolateState, Value, - Option, + &mut [ZeroCopyBuf], ) -> Result where - D: Fn(&State, Value, Option) -> Result, + D: Fn(&State, Value, &mut [ZeroCopyBuf]) -> Result, { let state = self.clone(); - move |_isolate: &mut deno_core::CoreIsolate, + move |_isolate_state: &mut deno_core::CoreIsolateState, args: Value, - zero_copy: Option| + zero_copy: &mut [ZeroCopyBuf]| -> Result { dispatcher(&state, args, zero_copy) } } @@ -204,24 +204,24 @@ impl State { &self, dispatcher: D, ) -> impl Fn( - &mut deno_core::CoreIsolate, + &mut deno_core::CoreIsolateState, Value, - Option, + &mut [ZeroCopyBuf], ) -> Result where D: Fn( - &mut deno_core::CoreIsolate, + &mut deno_core::CoreIsolateState, &State, Value, - Option, + &mut [ZeroCopyBuf], ) -> Result, { let state = self.clone(); - move |isolate: &mut deno_core::CoreIsolate, + move |isolate_state: &mut deno_core::CoreIsolateState, args: Value, - zero_copy: Option| + zero_copy: &mut [ZeroCopyBuf]| -> Result { - dispatcher(isolate, &state, args, zero_copy) + dispatcher(isolate_state, &state, args, zero_copy) } } @@ -268,76 +268,23 @@ impl ModuleLoader for State { Ok(module_specifier) } - /// Given an absolute url, load its source code. fn load( &self, module_specifier: &ModuleSpecifier, maybe_referrer: Option, - is_dyn_import: bool, + _is_dyn_import: bool, ) -> Pin> { - let module_specifier = module_specifier.clone(); - - // TODO(bartlomieju): this code is duplicated from module_graph. - // It should be removed when `prepare_load` will be used to load modules. - // Disallow http:// imports from modules loaded over https:// - if let Some(referrer) = maybe_referrer.as_ref() { - if let "https" = referrer.as_url().scheme() { - if let "http" = module_specifier.as_url().scheme() { - let e = OpError::permission_denied( - "Modules loaded over https:// are not allowed to import modules over http://".to_string() - ); - return async move { Err(e.into()) }.boxed_local(); - } - } - } - - if is_dyn_import { - if let Err(e) = self.check_dyn_import(&module_specifier) { - return async move { Err(e.into()) }.boxed_local(); - } - } else { - // Verify that remote file doesn't try to statically import local file. - if let Some(referrer) = maybe_referrer.as_ref() { - let referrer_url = referrer.as_url(); - match referrer_url.scheme() { - "http" | "https" => { - let specifier_url = module_specifier.as_url(); - match specifier_url.scheme() { - "http" | "https" => {} - _ => { - let e = OpError::permission_denied( - "Remote modules are not allowed to statically import local modules. Use dynamic import instead.".to_string() - ); - return async move { Err(e.into()) }.boxed_local(); - } - } - } - _ => {} - } - } - } - + let module_specifier = module_specifier.to_owned(); let mut state = self.borrow_mut(); // TODO(bartlomieju): incrementing resolve_count here has no sense... state.metrics.resolve_count += 1; let module_url_specified = module_specifier.to_string(); let global_state = state.global_state.clone(); - let target_lib = state.target_lib.clone(); - let permissions = if state.is_main { - Permissions::allow_all() - } else { - state.permissions.clone() - }; + // TODO(bartlomieju): `fetch_compiled_module` should take `load_id` param let fut = async move { let compiled_module = global_state - .fetch_compiled_module( - module_specifier, - maybe_referrer, - target_lib, - permissions, - is_dyn_import, - ) + .fetch_compiled_module(module_specifier, maybe_referrer) .await?; Ok(deno_core::ModuleSource { // Real module name, might be different from initial specifier @@ -354,22 +301,47 @@ impl ModuleLoader for State { fn prepare_load( &self, _load_id: ModuleLoadId, - _module_specifier: &ModuleSpecifier, - _maybe_referrer: Option, - _is_dyn_import: bool, + module_specifier: &ModuleSpecifier, + maybe_referrer: Option, + is_dyn_import: bool, ) -> Pin>>> { - // TODO(bartlomieju): - // 1. recursively: - // a) resolve specifier - // b) check permission if dynamic import - // c) fetch/download source code - // d) parse the source code and extract all import/exports (dependencies) - // e) add discovered deps and loop algorithm until no new dependencies - // are discovered - // 2. run through appropriate compiler giving it access only to - // discovered files - - async { Ok(()) }.boxed_local() + let module_specifier = module_specifier.clone(); + let state = self.borrow(); + let target_lib = state.target_lib.clone(); + let maybe_import_map = state.import_map.clone(); + // Only "main" module is loaded without permission check, + // ie. module that is associated with "is_main" state + // and is not a dynamic import. + let permissions = if state.is_main && !is_dyn_import { + Permissions::allow_all() + } else { + state.permissions.clone() + }; + let global_state = state.global_state.clone(); + // TODO(bartlomieju): I'm not sure if it's correct to ignore + // bad referrer - this is the case for `Deno.core.evalContext()` where + // `ref_str` is ``. + let maybe_referrer = if let Some(ref_str) = maybe_referrer { + ModuleSpecifier::resolve_url(&ref_str).ok() + } else { + None + }; + drop(state); + + // TODO(bartlomieju): `prepare_module_load` should take `load_id` param + async move { + global_state + .prepare_module_load( + module_specifier, + maybe_referrer, + target_lib, + permissions, + is_dyn_import, + maybe_import_map, + ) + .await + } + .boxed_local() } } @@ -379,19 +351,9 @@ impl State { global_state: GlobalState, shared_permissions: Option, main_module: ModuleSpecifier, + maybe_import_map: Option, is_internal: bool, ) -> Result { - let import_map: Option = - match global_state.flags.import_map_path.as_ref() { - None => None, - Some(file_path) => { - if !global_state.flags.unstable { - exit_unstable("--importmap") - } - Some(ImportMap::load(file_path)?) - } - }; - let seeded_rng = match global_state.flags.seed { Some(seed) => Some(StdRng::seed_from_u64(seed)), None => None, @@ -407,7 +369,7 @@ impl State { global_state, main_module, permissions, - import_map, + import_map: maybe_import_map, metrics: Metrics::default(), global_timer: GlobalTimer::new(), workers: HashMap::new(), @@ -463,6 +425,17 @@ impl State { self.borrow().permissions.check_read(path) } + /// As `check_read()`, but permission error messages will anonymize the path + /// by replacing it with the given `display`. + #[inline] + pub fn check_read_blind( + &self, + path: &Path, + display: &str, + ) -> Result<(), OpError> { + self.borrow().permissions.check_read_blind(path, display) + } + #[inline] pub fn check_write(&self, path: &Path) -> Result<(), OpError> { self.borrow().permissions.check_write(path) @@ -529,6 +502,7 @@ impl State { GlobalState::mock(vec!["deno".to_string()]), None, module_specifier, + None, false, ) .unwrap() diff --git a/cli/swc_util.rs b/cli/swc_util.rs index ce737215911426..0d75877bf39aff 100644 --- a/cli/swc_util.rs +++ b/cli/swc_util.rs @@ -1,4 +1,5 @@ // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. +use crate::doc::Location; use crate::msg::MediaType; use crate::swc_common; use crate::swc_common::comments::CommentKind; @@ -31,9 +32,9 @@ use swc_ecma_visit::Visit; fn get_default_es_config() -> EsConfig { let mut config = EsConfig::default(); config.num_sep = true; - config.class_private_props = false; - config.class_private_methods = false; - config.class_props = false; + config.class_private_props = true; + config.class_private_methods = true; + config.class_props = true; config.export_default_from = true; config.export_namespace_from = true; config.dynamic_import = true; @@ -450,6 +451,7 @@ fn get_deno_types(parser: &AstParser, span: Span) -> Option { pub struct ImportDescriptor { pub specifier: String, pub deno_types: Option, + pub location: Location, } #[derive(Clone, Debug, PartialEq)] @@ -463,6 +465,7 @@ pub enum TsReferenceKind { pub struct TsReferenceDescriptor { pub kind: TsReferenceKind, pub specifier: String, + pub location: Location, } pub fn analyze_dependencies_and_references( @@ -496,16 +499,19 @@ pub fn analyze_dependencies_and_references( desc.kind != DependencyKind::DynamicImport }) .map(|desc| { + let location = parser.get_span_location(desc.span); if desc.kind == DependencyKind::Import { let deno_types = get_deno_types(&parser, desc.span); ImportDescriptor { specifier: desc.specifier.to_string(), deno_types, + location: location.into(), } } else { ImportDescriptor { specifier: desc.specifier.to_string(), deno_types: None, + location: location.into(), } } }) @@ -515,7 +521,7 @@ pub fn analyze_dependencies_and_references( let comments = parser .comments .take_leading_comments(module_span.lo()) - .unwrap_or_else(|| vec![]); + .unwrap_or_else(Vec::new); let mut references = vec![]; for comment in comments { @@ -553,7 +559,12 @@ pub fn analyze_dependencies_and_references( .trim_end_matches('\'') .to_string(); - references.push(TsReferenceDescriptor { kind, specifier }); + let location = parser.get_span_location(comment.span); + references.push(TsReferenceDescriptor { + kind, + specifier, + location: location.into(), + }); } Ok((imports, references)) }) @@ -595,15 +606,30 @@ console.log(qat.qat); vec![ ImportDescriptor { specifier: "./type_definitions/foo.js".to_string(), - deno_types: Some("./type_definitions/foo.d.ts".to_string()) + deno_types: Some("./type_definitions/foo.d.ts".to_string()), + location: Location { + filename: "some/file.ts".to_string(), + line: 9, + col: 0, + }, }, ImportDescriptor { specifier: "./type_definitions/fizz.js".to_string(), - deno_types: Some("./type_definitions/fizz.d.ts".to_string()) + deno_types: Some("./type_definitions/fizz.d.ts".to_string()), + location: Location { + filename: "some/file.ts".to_string(), + line: 11, + col: 0, + }, }, ImportDescriptor { specifier: "./type_definitions/qat.ts".to_string(), - deno_types: None + deno_types: None, + location: Location { + filename: "some/file.ts".to_string(), + line: 15, + col: 0, + }, }, ] ); @@ -617,14 +643,29 @@ console.log(qat.qat); TsReferenceDescriptor { specifier: "dom".to_string(), kind: TsReferenceKind::Lib, + location: Location { + filename: "some/file.ts".to_string(), + line: 5, + col: 0, + }, }, TsReferenceDescriptor { specifier: "./type_reference.d.ts".to_string(), kind: TsReferenceKind::Types, + location: Location { + filename: "some/file.ts".to_string(), + line: 6, + col: 0, + }, }, TsReferenceDescriptor { specifier: "./type_reference/dep.ts".to_string(), kind: TsReferenceKind::Path, + location: Location { + filename: "some/file.ts".to_string(), + line: 7, + col: 0, + }, }, ] ); diff --git a/cli/tests/020_json_modules.ts.out b/cli/tests/020_json_modules.ts.out index 4369639eb4aa38..b4b56f94667198 100644 --- a/cli/tests/020_json_modules.ts.out +++ b/cli/tests/020_json_modules.ts.out @@ -1,9 +1,9 @@ [WILDCARD] error: Uncaught TypeError: Cannot resolve extension for "[WILDCARD]config.json" with mediaType "Json". - at getExtension ($deno$/compiler.ts:[WILDCARD]) - at new SourceFile ($deno$/compiler.ts:[WILDCARD]) - at Function.addToCache ($deno$/compiler.ts:[WILDCARD]) - at buildSourceFileCache ($deno$/compiler.ts:[WILDCARD]) - at compile ($deno$/compiler.ts:[WILDCARD]) - at tsCompilerOnMessage ($deno$/compiler.ts:[WILDCARD]) + at getExtension ($deno$/compiler.ts#[WILDCARD]) + at new SourceFile ($deno$/compiler.ts#[WILDCARD]) + at Function.addToCache ($deno$/compiler.ts#[WILDCARD]) + at buildSourceFileCache ($deno$/compiler.ts#[WILDCARD]) + at compile ($deno$/compiler.ts#[WILDCARD]) + at tsCompilerOnMessage ($deno$/compiler.ts#[WILDCARD]) [WILDCARD] \ No newline at end of file diff --git a/cli/tests/044_bad_resource.ts.out b/cli/tests/044_bad_resource.ts.out index 60985147738111..0df3237da24256 100644 --- a/cli/tests/044_bad_resource.ts.out +++ b/cli/tests/044_bad_resource.ts.out @@ -1,4 +1,4 @@ [WILDCARD]error: Uncaught BadResource: Bad resource ID - at unwrapResponse ([WILDCARD]dispatch_json.ts:[WILDCARD]) - at Object.sendAsync ([WILDCARD]dispatch_json.ts:[WILDCARD]) - at async main ([WILDCARD]tests/044_bad_resource.ts:[WILDCARD]) + at unwrapResponse ([WILDCARD]dispatch_json.ts#[WILDCARD]) + at Object.sendAsync ([WILDCARD]dispatch_json.ts#[WILDCARD]) + at async main ([WILDCARD]tests/044_bad_resource.ts#[WILDCARD]) diff --git a/cli/tests/056_make_temp_file_write_perm.ts b/cli/tests/056_make_temp_file_write_perm.ts index 15aefaff996cd0..c0deda8a25c305 100644 --- a/cli/tests/056_make_temp_file_write_perm.ts +++ b/cli/tests/056_make_temp_file_write_perm.ts @@ -1,8 +1,9 @@ -const path = await Deno.makeTempFile({ dir: "./subdir/" }); -if (path.startsWith(Deno.cwd())) { +const path = await Deno.makeTempFile({ dir: `subdir` }); +try { + if (!path.match(/^subdir[/\\][^/\\]+/)) { + throw Error("bad " + path); + } console.log("good", path); -} else { - throw Error("bad " + path); +} finally { + await Deno.remove(path); } -console.log(path); -await Deno.remove(path); diff --git a/cli/tests/059_fs_relative_path_perm.ts b/cli/tests/059_fs_relative_path_perm.ts new file mode 100644 index 00000000000000..26630fe1c4652d --- /dev/null +++ b/cli/tests/059_fs_relative_path_perm.ts @@ -0,0 +1,2 @@ +// The permission error message shouldn't include the CWD. +Deno.readFileSync("non-existent"); diff --git a/cli/tests/059_fs_relative_path_perm.ts.out b/cli/tests/059_fs_relative_path_perm.ts.out new file mode 100644 index 00000000000000..83df4190397de0 --- /dev/null +++ b/cli/tests/059_fs_relative_path_perm.ts.out @@ -0,0 +1,2 @@ +[WILDCARD]error: Uncaught PermissionDenied: read access to "non-existent", run again with the --allow-read flag + at [WILDCARD] diff --git a/cli/tests/async_error.ts.out b/cli/tests/async_error.ts.out index 7e32cd3dd2fa13..273f60794f6d19 100644 --- a/cli/tests/async_error.ts.out +++ b/cli/tests/async_error.ts.out @@ -4,5 +4,5 @@ world error: Uncaught Error: error throw Error("error"); ^ - at foo ([WILDCARD]tests/async_error.ts:5:9) - at [WILDCARD]tests/async_error.ts:8:1 + at foo ([WILDCARD]tests/async_error.ts#5:9) + at [WILDCARD]tests/async_error.ts#8:1 diff --git a/cli/tests/bundle.test.out b/cli/tests/bundle.test.out index c3e12e8a914c3e..b286dad485b0cc 100644 --- a/cli/tests/bundle.test.out +++ b/cli/tests/bundle.test.out @@ -6,17 +6,13 @@ let System, __instantiateAsync, __instantiate; })(); System.register("print_hello", [], function (exports_1, context_1) { - [WILDCARD] +[WILDCARD] +}); +System.register("subdir2/mod2", ["print_hello"], function (exports_2, context_2) { +[WILDCARD] }); -System.register( - "subdir2/mod2", - ["print_hello"], - function (exports_2, context_2) { - [WILDCARD] - }, -); System.register("mod1", ["subdir2/mod2"], function (exports_3, context_3) { - [WILDCARD] +[WILDCARD] }); const __exp = __instantiate("mod1"); diff --git a/cli/tests/cjs_imports.ts b/cli/tests/cjs_imports.ts new file mode 100644 index 00000000000000..d8b77c22ebff69 --- /dev/null +++ b/cli/tests/cjs_imports.ts @@ -0,0 +1 @@ +import "./commonjs.cjs"; diff --git a/cli/tests/cjs_imports.ts.out b/cli/tests/cjs_imports.ts.out new file mode 100644 index 00000000000000..557db03de997c8 --- /dev/null +++ b/cli/tests/cjs_imports.ts.out @@ -0,0 +1 @@ +Hello World diff --git a/cli/tests/commonjs.cjs b/cli/tests/commonjs.cjs new file mode 100644 index 00000000000000..7df7d571e68fe2 --- /dev/null +++ b/cli/tests/commonjs.cjs @@ -0,0 +1 @@ +console.log("Hello World"); \ No newline at end of file diff --git a/cli/tests/disallow_http_from_https_js.out b/cli/tests/disallow_http_from_https_js.out index 7b71cb6bfebc3d..e4e42115905852 100644 --- a/cli/tests/disallow_http_from_https_js.out +++ b/cli/tests/disallow_http_from_https_js.out @@ -1 +1,2 @@ error: Modules loaded over https:// are not allowed to import modules over http:// +Imported from "https://localhost:5545/cli/tests/disallow_http_from_https.js:2" diff --git a/cli/tests/disallow_http_from_https_ts.out b/cli/tests/disallow_http_from_https_ts.out index 7b71cb6bfebc3d..55e10b73336521 100644 --- a/cli/tests/disallow_http_from_https_ts.out +++ b/cli/tests/disallow_http_from_https_ts.out @@ -1 +1,2 @@ error: Modules loaded over https:// are not allowed to import modules over http:// +Imported from "https://localhost:5545/cli/tests/disallow_http_from_https.ts:2" diff --git a/cli/tests/error_001.ts.out b/cli/tests/error_001.ts.out index d4103349a2ac8f..39d5ba1adf8313 100644 --- a/cli/tests/error_001.ts.out +++ b/cli/tests/error_001.ts.out @@ -1,6 +1,6 @@ [WILDCARD]error: Uncaught Error: bad throw Error("bad"); ^ - at foo ([WILDCARD]tests/error_001.ts:2:9) - at bar ([WILDCARD]tests/error_001.ts:6:3) - at [WILDCARD]tests/error_001.ts:9:1 + at foo ([WILDCARD]tests/error_001.ts#2:9) + at bar ([WILDCARD]tests/error_001.ts#6:3) + at [WILDCARD]tests/error_001.ts#9:1 diff --git a/cli/tests/error_002.ts.out b/cli/tests/error_002.ts.out index 2aae770f8a4916..613cd627cef9de 100644 --- a/cli/tests/error_002.ts.out +++ b/cli/tests/error_002.ts.out @@ -1,6 +1,6 @@ [WILDCARD]error: Uncaught Error: exception from mod1 throw Error("exception from mod1"); ^ - at throwsError ([WILDCARD]tests/subdir/mod1.ts:16:9) - at foo ([WILDCARD]tests/error_002.ts:4:3) - at [WILDCARD]tests/error_002.ts:7:1 + at throwsError ([WILDCARD]tests/subdir/mod1.ts#16:9) + at foo ([WILDCARD]tests/error_002.ts#4:3) + at [WILDCARD]tests/error_002.ts#7:1 diff --git a/cli/tests/error_005_missing_dynamic_import.ts.out b/cli/tests/error_005_missing_dynamic_import.ts.out index 346e8cd6f99483..8a64175ec4dd1e 100644 --- a/cli/tests/error_005_missing_dynamic_import.ts.out +++ b/cli/tests/error_005_missing_dynamic_import.ts.out @@ -1 +1 @@ -error: Cannot resolve module "[WILDCARD]/bad-module.ts" from "[WILDCARD]/error_005_missing_dynamic_import.ts" +error: Uncaught TypeError: Cannot resolve module "[WILDCARD]/bad-module.ts" from "[WILDCARD]/error_005_missing_dynamic_import.ts" diff --git a/cli/tests/error_008_checkjs.js.out b/cli/tests/error_008_checkjs.js.out index 2d93ffd017511a..098bbc51f61236 100644 --- a/cli/tests/error_008_checkjs.js.out +++ b/cli/tests/error_008_checkjs.js.out @@ -1,4 +1,4 @@ [WILDCARD]error: Uncaught ReferenceError: consol is not defined consol.log("hello world!"); ^ - at [WILDCARD]tests/error_008_checkjs.js:2:1 + at [WILDCARD]tests/error_008_checkjs.js#2:1 diff --git a/cli/tests/error_012_bad_dynamic_import_specifier.ts.out b/cli/tests/error_012_bad_dynamic_import_specifier.ts.out index 7bebeda12f2519..78770dc817aa02 100644 --- a/cli/tests/error_012_bad_dynamic_import_specifier.ts.out +++ b/cli/tests/error_012_bad_dynamic_import_specifier.ts.out @@ -1 +1,2 @@ -error: relative import path "bad-module.ts" not prefixed with / or ./ or ../ Imported from "[WILDCARD]/error_012_bad_dynamic_import_specifier.ts" +Compile [WILDCARD]error_012_bad_dynamic_import_specifier.ts +error: Uncaught TypeError: relative import path "bad-module.ts" not prefixed with / or ./ or ../ Imported from "[WILDCARD]/error_012_bad_dynamic_import_specifier.ts" diff --git a/cli/tests/error_014_catch_dynamic_import_error.js.out b/cli/tests/error_014_catch_dynamic_import_error.js.out index 1a2ab2fd8757e4..db06423a6cde2d 100644 --- a/cli/tests/error_014_catch_dynamic_import_error.js.out +++ b/cli/tests/error_014_catch_dynamic_import_error.js.out @@ -6,7 +6,7 @@ TypeError: relative import path "does not exist either" not prefixed with / or . Caught error thrown by dynamically imported module. Error: An error - at file:///[WILDCARD]tests/subdir/throws.js:5:7 + at file:///[WILDCARD]tests/subdir/throws.js#5:7 Caught error thrown indirectly by dynamically imported module. Error: An error - at file:///[WILDCARD]tests/subdir/throws.js:5:7 + at file:///[WILDCARD]tests/subdir/throws.js#5:7 diff --git a/cli/tests/error_018_hide_long_source_js.js.out b/cli/tests/error_018_hide_long_source_js.js.out index 37265d659ecdd8..ab03b11147c38f 100644 --- a/cli/tests/error_018_hide_long_source_js.js.out +++ b/cli/tests/error_018_hide_long_source_js.js.out @@ -1,2 +1,2 @@ error: Uncaught TypeError: Cannot read property 'a' of undefined - at file:///[WILDCARD]cli/tests/error_018_hide_long_source_js.js:2:206 + at file:///[WILDCARD]cli/tests/error_018_hide_long_source_js.js#2:206 diff --git a/cli/tests/error_019_stack_function.ts.out b/cli/tests/error_019_stack_function.ts.out index a360e7d8ed9e5f..5bcd79cdd50387 100644 --- a/cli/tests/error_019_stack_function.ts.out +++ b/cli/tests/error_019_stack_function.ts.out @@ -1,6 +1,6 @@ [WILDCARD]Error: function - at foo ([WILDCARD]tests/error_019_stack_function.ts:[WILDCARD]) - at [WILDCARD]tests/error_019_stack_function.ts:[WILDCARD] + at foo ([WILDCARD]tests/error_019_stack_function.ts#[WILDCARD]) + at [WILDCARD]tests/error_019_stack_function.ts#[WILDCARD] error: Uncaught Error: function - at foo ([WILDCARD]tests/error_019_stack_function.ts:[WILDCARD]) - at [WILDCARD]tests/error_019_stack_function.ts:[WILDCARD] + at foo ([WILDCARD]tests/error_019_stack_function.ts#[WILDCARD]) + at [WILDCARD]tests/error_019_stack_function.ts#[WILDCARD] diff --git a/cli/tests/error_020_stack_constructor.ts.out b/cli/tests/error_020_stack_constructor.ts.out index a60e375d71b0bf..f1333f8797cb86 100644 --- a/cli/tests/error_020_stack_constructor.ts.out +++ b/cli/tests/error_020_stack_constructor.ts.out @@ -1,6 +1,6 @@ [WILDCARD]Error: constructor - at new A ([WILDCARD]tests/error_020_stack_constructor.ts:[WILDCARD]) - at [WILDCARD]tests/error_020_stack_constructor.ts:[WILDCARD] + at new A ([WILDCARD]tests/error_020_stack_constructor.ts#[WILDCARD]) + at [WILDCARD]tests/error_020_stack_constructor.ts#[WILDCARD] error: Uncaught Error: constructor - at new A ([WILDCARD]tests/error_020_stack_constructor.ts:[WILDCARD]) - at [WILDCARD]tests/error_020_stack_constructor.ts:[WILDCARD] + at new A ([WILDCARD]tests/error_020_stack_constructor.ts#[WILDCARD]) + at [WILDCARD]tests/error_020_stack_constructor.ts#[WILDCARD] diff --git a/cli/tests/error_021_stack_method.ts.out b/cli/tests/error_021_stack_method.ts.out index fff3e193b06125..fa627aed13da7d 100644 --- a/cli/tests/error_021_stack_method.ts.out +++ b/cli/tests/error_021_stack_method.ts.out @@ -1,6 +1,6 @@ [WILDCARD]Error: method - at A.m ([WILDCARD]tests/error_021_stack_method.ts:[WILDCARD]) - at [WILDCARD]tests/error_021_stack_method.ts:[WILDCARD] + at A.m ([WILDCARD]tests/error_021_stack_method.ts#[WILDCARD]) + at [WILDCARD]tests/error_021_stack_method.ts#[WILDCARD] error: Uncaught Error: method - at A.m ([WILDCARD]tests/error_021_stack_method.ts:[WILDCARD]) - at [WILDCARD]tests/error_021_stack_method.ts:[WILDCARD] + at A.m ([WILDCARD]tests/error_021_stack_method.ts#[WILDCARD]) + at [WILDCARD]tests/error_021_stack_method.ts#[WILDCARD] diff --git a/cli/tests/error_022_stack_custom_error.ts.out b/cli/tests/error_022_stack_custom_error.ts.out index 89c536ddb19ac8..2983764c49cb83 100644 --- a/cli/tests/error_022_stack_custom_error.ts.out +++ b/cli/tests/error_022_stack_custom_error.ts.out @@ -1,4 +1,4 @@ [WILDCARD]CustomError: custom error - at [WILDCARD]tests/error_022_stack_custom_error.ts:[WILDCARD] + at [WILDCARD]tests/error_022_stack_custom_error.ts#[WILDCARD] error: Uncaught CustomError: custom error - at [WILDCARD]tests/error_022_stack_custom_error.ts:[WILDCARD] + at [WILDCARD]tests/error_022_stack_custom_error.ts#[WILDCARD] diff --git a/cli/tests/error_023_stack_async.ts.out b/cli/tests/error_023_stack_async.ts.out index e5b707ce452545..bb0930da9886cd 100644 --- a/cli/tests/error_023_stack_async.ts.out +++ b/cli/tests/error_023_stack_async.ts.out @@ -1,8 +1,8 @@ [WILDCARD]Error: async - at [WILDCARD]tests/error_023_stack_async.ts:[WILDCARD] - at async [WILDCARD]tests/error_023_stack_async.ts:[WILDCARD] - at async [WILDCARD]tests/error_023_stack_async.ts:[WILDCARD] + at [WILDCARD]tests/error_023_stack_async.ts#[WILDCARD] + at async [WILDCARD]tests/error_023_stack_async.ts#[WILDCARD] + at async [WILDCARD]tests/error_023_stack_async.ts#[WILDCARD] error: Uncaught Error: async - at [WILDCARD]tests/error_023_stack_async.ts:[WILDCARD] - at async [WILDCARD]tests/error_023_stack_async.ts:[WILDCARD] - at async [WILDCARD]tests/error_023_stack_async.ts:[WILDCARD] + at [WILDCARD]tests/error_023_stack_async.ts#[WILDCARD] + at async [WILDCARD]tests/error_023_stack_async.ts#[WILDCARD] + at async [WILDCARD]tests/error_023_stack_async.ts#[WILDCARD] diff --git a/cli/tests/error_024_stack_promise_all.ts.out b/cli/tests/error_024_stack_promise_all.ts.out index 38cb4ac3137493..bfec5065b29bc4 100644 --- a/cli/tests/error_024_stack_promise_all.ts.out +++ b/cli/tests/error_024_stack_promise_all.ts.out @@ -1,8 +1,8 @@ [WILDCARD]Error: Promise.all() - at [WILDCARD]tests/error_024_stack_promise_all.ts:[WILDCARD] + at [WILDCARD]tests/error_024_stack_promise_all.ts#[WILDCARD] at async Promise.all (index 0) - at async [WILDCARD]tests/error_024_stack_promise_all.ts:[WILDCARD] + at async [WILDCARD]tests/error_024_stack_promise_all.ts#[WILDCARD] error: Uncaught Error: Promise.all() - at [WILDCARD]tests/error_024_stack_promise_all.ts:[WILDCARD] + at [WILDCARD]tests/error_024_stack_promise_all.ts#[WILDCARD] at async Promise.all (index 0) - at async [WILDCARD]tests/error_024_stack_promise_all.ts:[WILDCARD] + at async [WILDCARD]tests/error_024_stack_promise_all.ts#[WILDCARD] diff --git a/cli/tests/error_025_tab_indent.out b/cli/tests/error_025_tab_indent.out index 7b339c48fe83fb..1849a4b3d51b81 100644 --- a/cli/tests/error_025_tab_indent.out +++ b/cli/tests/error_025_tab_indent.out @@ -1,6 +1,6 @@ [WILDCARD]error: Uncaught Error: bad throw Error("bad"); ^ - at foo ([WILDCARD]tests/error_025_tab_indent:2:8) - at bar ([WILDCARD]tests/error_025_tab_indent:6:2) - at [WILDCARD]tests/error_025_tab_indent:9:1 + at foo ([WILDCARD]tests/error_025_tab_indent#2:8) + at bar ([WILDCARD]tests/error_025_tab_indent#6:2) + at [WILDCARD]tests/error_025_tab_indent#9:1 diff --git a/cli/tests/error_local_static_import_from_remote.js.out b/cli/tests/error_local_static_import_from_remote.js.out index c3fda12744bf95..1a2dcb2e3ea943 100644 --- a/cli/tests/error_local_static_import_from_remote.js.out +++ b/cli/tests/error_local_static_import_from_remote.js.out @@ -1,2 +1,3 @@ [WILDCARD] error: Remote modules are not allowed to statically import local modules. Use dynamic import instead. +Imported from "[WILDCARD]error_local_static_import_from_remote.js:1" diff --git a/cli/tests/error_local_static_import_from_remote.ts.out b/cli/tests/error_local_static_import_from_remote.ts.out index c3fda12744bf95..a2f2e1bbf1e79d 100644 --- a/cli/tests/error_local_static_import_from_remote.ts.out +++ b/cli/tests/error_local_static_import_from_remote.ts.out @@ -1,2 +1,3 @@ [WILDCARD] error: Remote modules are not allowed to statically import local modules. Use dynamic import instead. +Imported from "[WILDCARD]error_local_static_import_from_remote.ts:1" diff --git a/cli/tests/error_syntax.js.out b/cli/tests/error_syntax.js.out index 202e04a32cd4a4..107bc1df0ddd1a 100644 --- a/cli/tests/error_syntax.js.out +++ b/cli/tests/error_syntax.js.out @@ -1,4 +1 @@ -error: Uncaught SyntaxError: Unexpected identifier -(the following is a syntax error ^^ ! ) - ~~~~~~~~~ - at [WILDCARD]tests/error_syntax.js:3:6 +error: Expected Comma, got Some(Word(following)) at [WILDCARD]tests/error_syntax.js:3:5 diff --git a/cli/tests/error_syntax_empty_trailing_line.mjs.out b/cli/tests/error_syntax_empty_trailing_line.mjs.out index 6e8a268e9d8889..3b78a23a65f60a 100644 --- a/cli/tests/error_syntax_empty_trailing_line.mjs.out +++ b/cli/tests/error_syntax_empty_trailing_line.mjs.out @@ -1,2 +1 @@ -error: Uncaught SyntaxError: Unexpected end of input - at [WILDCARD]tests/error_syntax_empty_trailing_line.mjs:[WILDCARD] +error: Unexpected eof at [WILDCARD]tests/error_syntax_empty_trailing_line.mjs:2:21 diff --git a/cli/tests/es_private_fields.js b/cli/tests/es_private_fields.js new file mode 100644 index 00000000000000..b5f83e39c79913 --- /dev/null +++ b/cli/tests/es_private_fields.js @@ -0,0 +1,15 @@ +class Foo { + #field = "field"; + + setValue(val) { + this.#field = val; + } + + getValue() { + return this.#field; + } +} + +const bar = new Foo(); +bar.setValue("PRIVATE"); +console.log(bar.getValue()); diff --git a/cli/tests/es_private_fields.js.out b/cli/tests/es_private_fields.js.out new file mode 100644 index 00000000000000..be1970b05340d6 --- /dev/null +++ b/cli/tests/es_private_fields.js.out @@ -0,0 +1 @@ +PRIVATE diff --git a/cli/tests/file_exists.ts b/cli/tests/file_exists.ts new file mode 100644 index 00000000000000..5fc5414b389967 --- /dev/null +++ b/cli/tests/file_exists.ts @@ -0,0 +1,6 @@ +try { + await Deno.open(Deno.args[0]); + Deno.exit(0); +} catch (e) { + Deno.exit(1); +} diff --git a/cli/tests/integration_tests.rs b/cli/tests/integration_tests.rs index a9d3ebd6c194a5..54d88b87407a64 100644 --- a/cli/tests/integration_tests.rs +++ b/cli/tests/integration_tests.rs @@ -412,6 +412,71 @@ fn js_unit_tests() { assert!(status.success()); } +#[test] +fn ts_dependency_recompilation() { + let t = TempDir::new().expect("tempdir fail"); + let ats = t.path().join("a.ts"); + + std::fs::write( + &ats, + " + import { foo } from \"./b.ts\"; + + function print(str: string): void { + console.log(str); + } + + print(foo);", + ) + .unwrap(); + + let bts = t.path().join("b.ts"); + std::fs::write( + &bts, + " + export const foo = \"foo\";", + ) + .unwrap(); + + let output = util::deno_cmd() + .current_dir(util::root_path()) + .env("NO_COLOR", "1") + .arg("run") + .arg(&ats) + .output() + .expect("failed to spawn script"); + + let stdout_output = std::str::from_utf8(&output.stdout).unwrap().trim(); + let stderr_output = std::str::from_utf8(&output.stderr).unwrap().trim(); + + assert!(stdout_output.ends_with("foo")); + assert!(stderr_output.starts_with("Compile")); + + // Overwrite contents of b.ts and run again + std::fs::write( + &bts, + " + export const foo = 5;", + ) + .expect("error writing file"); + + let output = util::deno_cmd() + .current_dir(util::root_path()) + .env("NO_COLOR", "1") + .arg("run") + .arg(&ats) + .output() + .expect("failed to spawn script"); + + let stdout_output = std::str::from_utf8(&output.stdout).unwrap().trim(); + let stderr_output = std::str::from_utf8(&output.stderr).unwrap().trim(); + + // error: TS2345 [ERROR]: Argument of type '5' is not assignable to parameter of type 'string'. + assert!(stderr_output.contains("TS2345")); + assert!(!output.status.success()); + assert!(stdout_output.is_empty()); +} + #[test] fn bundle_exports() { // First we have to generate a bundle of some module that has exports. @@ -680,6 +745,18 @@ fn repl_test_console_log() { assert!(err.is_empty()); } +#[test] +fn repl_cwd() { + let (_out, err) = util::run_and_collect_output( + true, + "repl", + Some(vec!["Deno.cwd()"]), + Some(vec![("NO_COLOR".to_owned(), "1".to_owned())]), + false, + ); + assert!(err.is_empty()); +} + #[test] fn repl_test_eof() { let (out, err) = util::run_and_collect_output( @@ -693,6 +770,24 @@ fn repl_test_eof() { assert!(err.is_empty()); } +#[test] +fn repl_test_strict() { + let (_, err) = util::run_and_collect_output( + true, + "repl", + Some(vec![ + "let a = {};", + "Object.preventExtensions(a);", + "a.c = 1;", + ]), + None, + false, + ); + assert!(err.contains( + "Uncaught TypeError: Cannot add property c, object is not extensible" + )); +} + const REPL_MSG: &str = "exit using ctrl+d or close()\n"; #[test] @@ -1243,6 +1338,12 @@ itest!(_058_tasks_microtasks_close { output: "058_tasks_microtasks_close.ts.out", }); +itest!(_059_fs_relative_path_perm { + args: "run 059_fs_relative_path_perm.ts", + output: "059_fs_relative_path_perm.ts.out", + exit_code: 1, +}); + itest!(js_import_detect { args: "run --quiet --reload js_import_detect.ts", output: "js_import_detect.ts.out", @@ -1359,7 +1460,7 @@ itest!(error_004_missing_module { }); itest!(error_005_missing_dynamic_import { - args: "run --reload --allow-read error_005_missing_dynamic_import.ts", + args: "run --reload --allow-read --quiet error_005_missing_dynamic_import.ts", exit_code: 1, output: "error_005_missing_dynamic_import.ts.out", }); @@ -1406,7 +1507,7 @@ itest!(error_014_catch_dynamic_import_error { }); itest!(error_015_dynamic_import_permissions { - args: "run --reload error_015_dynamic_import_permissions.js", + args: "run --reload --quiet error_015_dynamic_import_permissions.js", output: "error_015_dynamic_import_permissions.out", exit_code: 1, http_server: true, @@ -1738,6 +1839,21 @@ itest!(fix_js_imports { output: "fix_js_imports.ts.out", }); +itest!(es_private_fields { + args: "run --quiet --reload es_private_fields.js", + output: "es_private_fields.js.out", +}); + +itest!(cjs_imports { + args: "run --quiet --reload cjs_imports.ts", + output: "cjs_imports.ts.out", +}); + +itest!(ts_import_from_js { + args: "run --quiet --reload ts_import_from_js.js", + output: "ts_import_from_js.js.out", +}); + itest!(proto_exploit { args: "run proto_exploit.js", output: "proto_exploit.js.out", @@ -1862,14 +1978,17 @@ fn cafile_bundle_remote_exports() { #[test] fn test_permissions_with_allow() { for permission in &util::PERMISSION_VARIANTS { - let (_, err) = util::run_and_collect_output( - true, - &format!("run --allow-{0} permission_test.ts {0}Required", permission), - None, - None, - false, - ); - assert!(!err.contains(util::PERMISSION_DENIED_PATTERN)); + let status = util::deno_cmd() + .current_dir(&util::tests_path()) + .arg("run") + .arg(format!("--allow-{0}", permission)) + .arg("permission_test.ts") + .arg(format!("{0}Required", permission)) + .spawn() + .unwrap() + .wait() + .unwrap(); + assert!(status.success()); } } @@ -1891,19 +2010,22 @@ fn test_permissions_without_allow() { fn test_permissions_rw_inside_project_dir() { const PERMISSION_VARIANTS: [&str; 2] = ["read", "write"]; for permission in &PERMISSION_VARIANTS { - let (_, err) = util::run_and_collect_output( - true, - &format!( - "run --allow-{0}={1} complex_permissions_test.ts {0} {2} {2}", + let status = util::deno_cmd() + .current_dir(&util::tests_path()) + .arg("run") + .arg(format!( + "--allow-{0}={1}", permission, - util::root_path().into_os_string().into_string().unwrap(), - "complex_permissions_test.ts" - ), - None, - None, - false, - ); - assert!(!err.contains(util::PERMISSION_DENIED_PATTERN)); + util::root_path().into_os_string().into_string().unwrap() + )) + .arg("complex_permissions_test.ts") + .arg(permission) + .arg("complex_permissions_test.ts") + .spawn() + .unwrap() + .wait() + .unwrap(); + assert!(status.success()); } } @@ -1940,24 +2062,27 @@ fn test_permissions_rw_outside_test_dir() { fn test_permissions_rw_inside_test_dir() { const PERMISSION_VARIANTS: [&str; 2] = ["read", "write"]; for permission in &PERMISSION_VARIANTS { - let (_, err) = util::run_and_collect_output( - true, - &format!( - "run --allow-{0}={1} complex_permissions_test.ts {0} {2}", + let status = util::deno_cmd() + .current_dir(&util::tests_path()) + .arg("run") + .arg(format!( + "--allow-{0}={1}", permission, util::root_path() .join("cli") .join("tests") .into_os_string() .into_string() - .unwrap(), - "complex_permissions_test.ts" - ), - None, - None, - false, - ); - assert!(!err.contains(util::PERMISSION_DENIED_PATTERN)); + .unwrap() + )) + .arg("complex_permissions_test.ts") + .arg(permission) + .arg("complex_permissions_test.ts") + .spawn() + .unwrap() + .wait() + .unwrap(); + assert!(status.success()); } } @@ -2012,17 +2137,18 @@ fn test_permissions_rw_inside_test_and_js_dir() { .into_string() .unwrap(); for permission in &PERMISSION_VARIANTS { - let (_, err) = util::run_and_collect_output( - true, - &format!( - "run --allow-{0}={1},{2} complex_permissions_test.ts {0} {3}", - permission, test_dir, js_dir, "complex_permissions_test.ts" - ), - None, - None, - false, - ); - assert!(!err.contains(util::PERMISSION_DENIED_PATTERN)); + let status = util::deno_cmd() + .current_dir(&util::tests_path()) + .arg("run") + .arg(format!("--allow-{0}={1},{2}", permission, test_dir, js_dir)) + .arg("complex_permissions_test.ts") + .arg(permission) + .arg("complex_permissions_test.ts") + .spawn() + .unwrap() + .wait() + .unwrap(); + assert!(status.success()); } } @@ -2030,17 +2156,18 @@ fn test_permissions_rw_inside_test_and_js_dir() { fn test_permissions_rw_relative() { const PERMISSION_VARIANTS: [&str; 2] = ["read", "write"]; for permission in &PERMISSION_VARIANTS { - let (_, err) = util::run_and_collect_output( - true, - &format!( - "run --allow-{0}=. complex_permissions_test.ts {0} complex_permissions_test.ts", - permission - ), - None, - None, - false, - ); - assert!(!err.contains(util::PERMISSION_DENIED_PATTERN)); + let status = util::deno_cmd() + .current_dir(&util::tests_path()) + .arg("run") + .arg(format!("--allow-{0}=.", permission)) + .arg("complex_permissions_test.ts") + .arg(permission) + .arg("complex_permissions_test.ts") + .spawn() + .unwrap() + .wait() + .unwrap(); + assert!(status.success()); } } @@ -2048,17 +2175,18 @@ fn test_permissions_rw_relative() { fn test_permissions_rw_no_prefix() { const PERMISSION_VARIANTS: [&str; 2] = ["read", "write"]; for permission in &PERMISSION_VARIANTS { - let (_, err) = util::run_and_collect_output( - true, - &format!( - "run --allow-{0}=tls/../ complex_permissions_test.ts {0} complex_permissions_test.ts", - permission - ), - None, - None, - false, - ); - assert!(!err.contains(util::PERMISSION_DENIED_PATTERN)); + let status = util::deno_cmd() + .current_dir(&util::tests_path()) + .arg("run") + .arg(format!("--allow-{0}=tls/../", permission)) + .arg("complex_permissions_test.ts") + .arg(permission) + .arg("complex_permissions_test.ts") + .spawn() + .unwrap() + .wait() + .unwrap(); + assert!(status.success()); } } @@ -2659,7 +2787,7 @@ mod util { pub const PERMISSION_DENIED_PATTERN: &str = "PermissionDenied"; lazy_static! { - static ref DENO_DIR: TempDir = { TempDir::new().expect("tempdir fail") }; + static ref DENO_DIR: TempDir = TempDir::new().expect("tempdir fail"); // STRIP_ANSI_RE and strip_ansi_codes are lifted from the "console" crate. // Copyright 2017 Armin Ronacher . MIT License. diff --git a/cli/tests/lock_write_fetch.ts b/cli/tests/lock_write_fetch.ts index 9e719059652c95..5a4dea0ce94cce 100644 --- a/cli/tests/lock_write_fetch.ts +++ b/cli/tests/lock_write_fetch.ts @@ -32,6 +32,8 @@ const fetchCheckProc = Deno.run({ const fetchCheckProcCode = (await fetchCheckProc.status()).code; console.log(`fetch check code: ${fetchCheckProcCode}`); +Deno.removeSync("./lock_write_fetch.json"); + const runProc = Deno.run({ stdout: "null", stderr: "null", @@ -39,7 +41,10 @@ const runProc = Deno.run({ Deno.execPath(), "run", "--lock=lock_write_fetch.json", - "https_import.ts", + "--lock-write", + "--allow-read", + "file_exists.ts", + "lock_write_fetch.json", ], }); diff --git a/cli/tests/ts_import_from_js.js b/cli/tests/ts_import_from_js.js new file mode 100644 index 00000000000000..e06ca15a2480a4 --- /dev/null +++ b/cli/tests/ts_import_from_js.js @@ -0,0 +1 @@ +import "./005_more_imports.ts"; diff --git a/cli/tests/ts_import_from_js.js.out b/cli/tests/ts_import_from_js.js.out new file mode 100644 index 00000000000000..e965047ad7c578 --- /dev/null +++ b/cli/tests/ts_import_from_js.js.out @@ -0,0 +1 @@ +Hello diff --git a/cli/tests/unit/blob_test.ts b/cli/tests/unit/blob_test.ts index 70f8fb3d5a2dc9..79e6f1f8f00ccd 100644 --- a/cli/tests/unit/blob_test.ts +++ b/cli/tests/unit/blob_test.ts @@ -90,3 +90,8 @@ unitTest(async function blobStream(): Promise { await read(); assertEquals(decode(bytes), "Hello World"); }); + +unitTest(function blobConstructorNameIsBlob(): void { + const blob = new Blob(); + assertEquals(blob.constructor.name, "Blob"); +}); diff --git a/cli/tests/unit/body_test.ts b/cli/tests/unit/body_test.ts index fd91b5ceddd2a3..a6df3102e67c68 100644 --- a/cli/tests/unit/body_test.ts +++ b/cli/tests/unit/body_test.ts @@ -42,8 +42,8 @@ unitTest( const body = buildBody(text); - // @ts-expect-error - body.contentType = "multipart/form-data;boundary=boundary"; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (body as any).contentType = "multipart/form-data;boundary=boundary"; const formData = await body.formData(); assert(formData.has("field_1")); @@ -62,8 +62,8 @@ unitTest( const body = buildBody(text); - // @ts-expect-error - body.contentType = "application/x-www-form-urlencoded"; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (body as any).contentType = "application/x-www-form-urlencoded"; const formData = await body.formData(); assert(formData.has("field_1")); diff --git a/cli/tests/unit/console_test.ts b/cli/tests/unit/console_test.ts index 35985dc1c23a48..98e43c7395887e 100644 --- a/cli/tests/unit/console_test.ts +++ b/cli/tests/unit/console_test.ts @@ -571,7 +571,7 @@ unitTest(function consoleTestStringifyIterable() { `[ <4 empty items>, 0, 0, <4 empty items> ]` ); - /* TODO(ry) Fix this test + /* TODO(ry) Fix this test const lWithEmptyEl = Array(200); lWithEmptyEl.fill(0, 50, 80); assertEquals( @@ -1070,6 +1070,36 @@ unitTest(function consoleTable(): void { │ 1 │ "你好" │ │ 2 │ "Amapá" │ └───────┴─────────┘ +` + ); + }); + mockConsole((console, out): void => { + console.table([ + [1, 2], + [3, 4], + ]); + assertEquals( + stripColor(out.toString()), + `┌───────┬───┬───┐ +│ (idx) │ 0 │ 1 │ +├───────┼───┼───┤ +│ 0 │ 1 │ 2 │ +│ 1 │ 3 │ 4 │ +└───────┴───┴───┘ +` + ); + }); + mockConsole((console, out): void => { + console.table({ 1: { a: 4, b: 5 }, 2: null, 3: { b: 6, c: 7 } }, ["b"]); + assertEquals( + stripColor(out.toString()), + `┌───────┬───┐ +│ (idx) │ b │ +├───────┼───┤ +│ 1 │ 5 │ +│ 2 │ │ +│ 3 │ 6 │ +└───────┴───┘ ` ); }); diff --git a/cli/tests/unit/dir_test.ts b/cli/tests/unit/dir_test.ts index dc05d9564ba18a..5b60b3dbaa082c 100644 --- a/cli/tests/unit/dir_test.ts +++ b/cli/tests/unit/dir_test.ts @@ -1,5 +1,5 @@ // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. -import { unitTest, assert, assertEquals } from "./test_util.ts"; +import { unitTest, assert, assertEquals, assertThrows } from "./test_util.ts"; unitTest({ perms: { read: true } }, function dirCwdNotNull(): void { assert(Deno.cwd() != null); @@ -29,32 +29,31 @@ unitTest({ perms: { read: true, write: true } }, function dirCwdError(): void { Deno.chdir(path); Deno.removeSync(path); try { - Deno.cwd(); - throw Error("current directory removed, should throw error"); - } catch (err) { - if (err instanceof Deno.errors.NotFound) { - assert(err.name === "NotFound"); - } else { - throw Error("raised different exception"); - } + assertThrows(() => { + Deno.cwd(); + }, Deno.errors.NotFound); + } finally { + Deno.chdir(initialdir); } - Deno.chdir(initialdir); } }); +unitTest({ perms: { read: false } }, function dirCwdPermError(): void { + assertThrows( + () => { + Deno.cwd(); + }, + Deno.errors.PermissionDenied, + "read access to , run again with the --allow-read flag" + ); +}); + unitTest( { perms: { read: true, write: true } }, function dirChdirError(): void { const path = Deno.makeTempDirSync() + "test"; - try { + assertThrows(() => { Deno.chdir(path); - throw Error("directory not available, should throw error"); - } catch (err) { - if (err instanceof Deno.errors.NotFound) { - assert(err.name === "NotFound"); - } else { - throw Error("raised different exception"); - } - } + }, Deno.errors.NotFound); } ); diff --git a/cli/tests/unit/dispatch_json_test.ts b/cli/tests/unit/dispatch_json_test.ts index 51c33befde219c..51c63a4dba2a38 100644 --- a/cli/tests/unit/dispatch_json_test.ts +++ b/cli/tests/unit/dispatch_json_test.ts @@ -2,9 +2,9 @@ import { assert, unitTest, assertMatch, unreachable } from "./test_util.ts"; const openErrorStackPattern = new RegExp( `^.* - at unwrapResponse \\(.*dispatch_json\\.ts:.*\\) - at Object.sendAsync \\(.*dispatch_json\\.ts:.*\\) - at async Object\\.open \\(.*files\\.ts:.*\\).*$`, + at unwrapResponse \\(.*dispatch_json\\.ts#.*\\) + at Object.sendAsync \\(.*dispatch_json\\.ts#.*\\) + at async Object\\.open \\(.*files\\.ts#.*\\).*$`, "ms" ); @@ -19,14 +19,19 @@ unitTest( } ); +/* eslint-disable @typescript-eslint/no-namespace, @typescript-eslint/no-explicit-any,no-var */ +declare global { + namespace Deno { + var core: any; + } +} +/* eslint-enable */ + unitTest(function malformedJsonControlBuffer(): void { - // @ts-expect-error const opId = Deno.core.ops()["op_open"]; - // @ts-expect-error const res = Deno.core.send(opId, new Uint8Array([1, 2, 3, 4, 5])); const resText = new TextDecoder().decode(res); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const resJson = JSON.parse(resText) as any; + const resJson = JSON.parse(resText); assert(!resJson.ok); assert(resJson.err); }); diff --git a/cli/tests/unit/dispatch_minimal_test.ts b/cli/tests/unit/dispatch_minimal_test.ts index 1c7ba11f0f99e0..98f482f7340cf8 100644 --- a/cli/tests/unit/dispatch_minimal_test.ts +++ b/cli/tests/unit/dispatch_minimal_test.ts @@ -8,9 +8,9 @@ import { const readErrorStackPattern = new RegExp( `^.* - at unwrapResponse \\(.*dispatch_minimal\\.ts:.*\\) - at Object.sendAsyncMinimal \\(.*dispatch_minimal\\.ts:.*\\) - at async Object\\.read \\(.*io\\.ts:.*\\).*$`, + at unwrapResponse \\(.*dispatch_minimal\\.ts#.*\\) + at Object.sendAsyncMinimal \\(.*dispatch_minimal\\.ts#.*\\) + at async Object\\.read \\(.*io\\.ts#.*\\).*$`, "ms" ); @@ -25,10 +25,16 @@ unitTest(async function sendAsyncStackTrace(): Promise { } }); +/* eslint-disable @typescript-eslint/no-namespace, @typescript-eslint/no-explicit-any,no-var */ +declare global { + namespace Deno { + var core: any; + } +} +/* eslint-enable */ + unitTest(function malformedMinimalControlBuffer(): void { - // @ts-expect-error const readOpId = Deno.core.ops()["op_read"]; - // @ts-expect-error const res = Deno.core.send(readOpId, new Uint8Array([1, 2, 3, 4, 5])); const header = res.slice(0, 12); const buf32 = new Int32Array( diff --git a/cli/tests/unit/error_stack_test.ts b/cli/tests/unit/error_stack_test.ts index eb0a5c0e6671f1..c3aed3886c5097 100644 --- a/cli/tests/unit/error_stack_test.ts +++ b/cli/tests/unit/error_stack_test.ts @@ -1,5 +1,5 @@ // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. -import { unitTest, assert } from "./test_util.ts"; +import { assert, assertEquals, unitTest } from "./test_util.ts"; // @ts-expect-error TypeScript (as of 3.7) does not support indexing namespaces by symbol const { setPrepareStackTrace } = Deno[Deno.internal]; @@ -93,7 +93,19 @@ unitTest(function prepareStackTrace(): void { getMockCallSite("CLI_SNAPSHOT.js", 23, 0), ]); assert(result.startsWith("Error: foo\n")); - assert(result.includes(".ts:"), "should remap to something in 'js/'"); + assert(result.includes(".ts#"), "should remap to something in 'js/'"); +}); + +unitTest(function captureStackTrace(): void { + function foo(): void { + const error = new Error(); + const stack1 = error.stack!; + Error.captureStackTrace(error, foo); + const stack2 = error.stack!; + // stack2 should be stack1 without the first frame. + assertEquals(stack2, stack1.replace(/(?<=^[^\n]*\n)[^\n]*\n/, "")); + } + foo(); }); unitTest(function applySourceMap(): void { diff --git a/cli/tests/unit/fetch_test.ts b/cli/tests/unit/fetch_test.ts index 97b001b5525c36..7c054c9642ca2a 100644 --- a/cli/tests/unit/fetch_test.ts +++ b/cli/tests/unit/fetch_test.ts @@ -92,31 +92,60 @@ unitTest({ perms: { net: true } }, async function fetchBodyUsed(): Promise< const response = await fetch("http://localhost:4545/cli/tests/fixture.json"); assertEquals(response.bodyUsed, false); assertThrows((): void => { - // Assigning to read-only property throws in the strict mode. - // @ts-expect-error - response.bodyUsed = true; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (response as any).bodyUsed = true; }); await response.blob(); assertEquals(response.bodyUsed, true); }); -// TODO(ry) response.body shouldn't be iterable. Instead we should use -// response.body.getReader(). -/* +unitTest( + { perms: { net: true } }, + async function fetchBodyUsedReader(): Promise { + const response = await fetch( + "http://localhost:4545/cli/tests/fixture.json" + ); + assert(response.body !== null); + + const reader = response.body.getReader(); + // Getting a reader should lock the stream but does not consume the body + // so bodyUsed should not be true + assertEquals(response.bodyUsed, false); + reader.releaseLock(); + await response.json(); + assertEquals(response.bodyUsed, true); + } +); + +unitTest( + { perms: { net: true } }, + async function fetchBodyUsedCancelStream(): Promise { + const response = await fetch( + "http://localhost:4545/cli/tests/fixture.json" + ); + assert(response.body !== null); + + assertEquals(response.bodyUsed, false); + const promise = response.body.cancel(); + assertEquals(response.bodyUsed, true); + await promise; + } +); + unitTest({ perms: { net: true } }, async function fetchAsyncIterator(): Promise< void > { const response = await fetch("http://localhost:4545/cli/tests/fixture.json"); const headers = response.headers; + + assert(response.body !== null); let total = 0; for await (const chunk of response.body) { total += chunk.length; } assertEquals(total, Number(headers.get("Content-Length"))); - const _json = await response.json(); }); -*/ unitTest({ perms: { net: true } }, async function fetchBodyReader(): Promise< void @@ -198,11 +227,10 @@ unitTest( assert(formData.has("field_1")); assertEquals(formData.get("field_1")!.toString(), "value_1 \r\n"); assert(formData.has("field_2")); - /* TODO(ry) Re-enable this test once we bring back the global File type. - const file = formData.get("field_2") as File; - assertEquals(file.name, "file.js"); - */ - // Currently we cannot read from file... + const file = formData.get("field_2") as File; + assertEquals(file.name, "file.js"); + + assertEquals(await file.text(), `console.log("Hi")`); } ); @@ -220,6 +248,25 @@ unitTest( } ); +unitTest( + { perms: { net: true } }, + async function fetchInitFormDataBinaryFileBody(): Promise { + // Some random bytes + // prettier-ignore + const binaryFile = new Uint8Array([108,2,0,0,145,22,162,61,157,227,166,77,138,75,180,56,119,188,177,183]); + const response = await fetch("http://localhost:4545/echo_multipart_file", { + method: "POST", + body: binaryFile, + }); + const resultForm = await response.formData(); + const resultFile = resultForm.get("file") as File; + + assertEquals(resultFile.type, "application/octet-stream"); + assertEquals(resultFile.name, "file.bin"); + assertEquals(new Uint8Array(await resultFile.arrayBuffer()), binaryFile); + } +); + unitTest( { perms: { net: true }, @@ -249,14 +296,13 @@ unitTest( unitTest( { - // FIXME(bartlomieju): - // The feature below is not implemented, but the test should work after implementation - ignore: true, perms: { net: true }, }, async function fetchWithInfRedirection(): Promise { const response = await fetch("http://localhost:4549/cli/tests"); // will redirect to the same place assertEquals(response.status, 0); // network error + assertEquals(response.type, "error"); + assertEquals(response.ok, false); } ); @@ -363,6 +409,24 @@ unitTest( } ); +unitTest( + { perms: { net: true } }, + async function fetchInitFormDataBlobFilenameBody(): Promise { + const form = new FormData(); + form.append("field", "value"); + form.append("file", new Blob([new TextEncoder().encode("deno")])); + const response = await fetch("http://localhost:4545/echo_server", { + method: "POST", + body: form, + }); + const resultForm = await response.formData(); + assertEquals(form.get("field"), resultForm.get("field")); + const file = resultForm.get("file"); + assert(file instanceof File); + assertEquals(file.name, "blob"); + } +); + unitTest({ perms: { net: true } }, async function fetchUserAgent(): Promise< void > { @@ -592,10 +656,9 @@ unitTest({ perms: { net: true } }, async function fetchBodyReadTwice(): Promise< assert(_json); // All calls after the body was consumed, should fail - const methods = ["json", "text", "formData", "arrayBuffer"]; + const methods = ["json", "text", "formData", "arrayBuffer"] as const; for (const method of methods) { try { - // @ts-expect-error await response[method](); fail( "Reading body multiple times should failed, the stream should've been locked." @@ -688,3 +751,73 @@ unitTest( assertEquals(total, data.length); } ); + +unitTest( + { perms: { net: true } }, + async function fetchResourceCloseAfterStreamCancel(): Promise { + const res = await fetch("http://localhost:4545/cli/tests/fixture.json"); + assert(res.body !== null); + + // After ReadableStream.cancel is called, resource handle must be closed + // The test should not fail with: Test case is leaking resources + await res.body.cancel(); + } +); + +unitTest( + { perms: { net: true } }, + async function fetchNullBodyStatus(): Promise { + const nullBodyStatus = [101, 204, 205, 304]; + + for (const status of nullBodyStatus) { + const headers = new Headers([["x-status", String(status)]]); + const res = await fetch("http://localhost:4545/cli/tests/echo_server", { + body: "deno", + method: "POST", + headers, + }); + assertEquals(res.body, null); + assertEquals(res.status, status); + } + } +); + +unitTest( + { perms: { net: true } }, + function fetchResponseConstructorNullBody(): void { + const nullBodyStatus = [204, 205, 304]; + + for (const status of nullBodyStatus) { + try { + new Response("deno", { status }); + fail("Response with null body status cannot have body"); + } catch (e) { + assert(e instanceof TypeError); + assertEquals( + e.message, + "Response with null body status cannot have body" + ); + } + } + } +); + +unitTest( + { perms: { net: true } }, + function fetchResponseConstructorInvalidStatus(): void { + const invalidStatus = [101, 600, 199]; + + for (const status of invalidStatus) { + try { + new Response("deno", { status }); + fail("Invalid status"); + } catch (e) { + assert(e instanceof RangeError); + assertEquals( + e.message, + `The status provided (${status}) is outside the range [200, 599]` + ); + } + } + } +); diff --git a/cli/tests/unit/files_test.ts b/cli/tests/unit/files_test.ts index 9ab74be89522e7..a1b5321574a5ef 100644 --- a/cli/tests/unit/files_test.ts +++ b/cli/tests/unit/files_test.ts @@ -290,8 +290,8 @@ unitTest( // writing null should throw an error let err; try { - // @ts-expect-error - await file.write(null); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + await file.write(null as any); } catch (e) { err = e; } @@ -322,8 +322,8 @@ unitTest( // reading file into null buffer should throw an error let err; try { - // @ts-expect-error - await file.read(null); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + await file.read(null as any); } catch (e) { err = e; } diff --git a/cli/tests/unit/form_data_test.ts b/cli/tests/unit/form_data_test.ts index 5344d8512ad719..de443889c5eedb 100644 --- a/cli/tests/unit/form_data_test.ts +++ b/cli/tests/unit/form_data_test.ts @@ -6,7 +6,7 @@ import { assertStrContains, } from "./test_util.ts"; -unitTest({ ignore: true }, function formDataHasCorrectNameProp(): void { +unitTest(function formDataHasCorrectNameProp(): void { assertEquals(FormData.name, "FormData"); }); @@ -41,10 +41,10 @@ unitTest(function formDataParamsGetSuccess(): void { formData.append("a", "true"); formData.append("b", "false"); formData.append("a", "null"); - // @ts-expect-error - formData.append("d", undefined); - // @ts-expect-error - formData.append("e", null); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + formData.append("d", undefined as any); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + formData.append("e", null as any); assertEquals(formData.get("a"), "true"); assertEquals(formData.get("b"), "false"); assertEquals(formData.get("c"), null); @@ -70,11 +70,11 @@ unitTest(function formDataParamsSetSuccess(): void { assertEquals(formData.getAll("b"), ["false"]); formData.set("a", "false"); assertEquals(formData.getAll("a"), ["false"]); - // @ts-expect-error - formData.set("d", undefined); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + formData.set("d", undefined as any); assertEquals(formData.get("d"), "undefined"); - // @ts-expect-error - formData.set("e", null); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + formData.set("e", null as any); assertEquals(formData.get("e"), "null"); }); @@ -99,6 +99,15 @@ unitTest(function formDataSetEmptyBlobSuccess(): void { */ }); +unitTest(function formDataBlobFilename(): void { + const formData = new FormData(); + const content = new TextEncoder().encode("deno"); + formData.set("a", new Blob([content])); + const file = formData.get("a"); + assert(file instanceof File); + assertEquals(file.name, "blob"); +}); + unitTest(function formDataParamsForEachSuccess(): void { const init = [ ["a", "54"], @@ -134,8 +143,8 @@ unitTest(function formDataParamsArgumentsCheck(): void { let hasThrown = 0; let errMsg = ""; try { - // @ts-expect-error - formData[method](); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (formData as any)[method](); hasThrown = 1; } catch (err) { errMsg = err.message; @@ -158,8 +167,8 @@ unitTest(function formDataParamsArgumentsCheck(): void { let errMsg = ""; try { - // @ts-expect-error - formData[method](); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (formData as any)[method](); hasThrown = 1; } catch (err) { errMsg = err.message; @@ -178,8 +187,8 @@ unitTest(function formDataParamsArgumentsCheck(): void { hasThrown = 0; errMsg = ""; try { - // @ts-expect-error - formData[method]("foo"); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (formData as any)[method]("foo"); hasThrown = 1; } catch (err) { errMsg = err.message; diff --git a/cli/tests/unit/globals_test.ts b/cli/tests/unit/globals_test.ts index bb5e5c604b5d69..ccea6e74c36f3e 100644 --- a/cli/tests/unit/globals_test.ts +++ b/cli/tests/unit/globals_test.ts @@ -45,16 +45,24 @@ unitTest(function webAssemblyExists(): void { assert(typeof WebAssembly.compile === "function"); }); +/* eslint-disable @typescript-eslint/no-namespace, @typescript-eslint/no-explicit-any,no-var */ +declare global { + namespace Deno { + var core: any; + } +} +/* eslint-enable */ + unitTest(function DenoNamespaceImmutable(): void { const denoCopy = window.Deno; try { - // @ts-expect-error - Deno = 1; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (Deno as any) = 1; } catch {} assert(denoCopy === Deno); try { - // @ts-expect-error - window.Deno = 1; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (window as any).Deno = 1; } catch {} assert(denoCopy === Deno); try { @@ -64,8 +72,8 @@ unitTest(function DenoNamespaceImmutable(): void { const { readFile } = Deno; try { - // @ts-expect-error - Deno.readFile = 1; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (Deno as any).readFile = 1; } catch {} assert(readFile === Deno.readFile); try { @@ -73,19 +81,14 @@ unitTest(function DenoNamespaceImmutable(): void { } catch {} assert(readFile === Deno.readFile); - // @ts-expect-error const { print } = Deno.core; try { - // @ts-expect-error Deno.core.print = 1; } catch {} - // @ts-expect-error assert(print === Deno.core.print); try { - // @ts-expect-error delete Deno.core.print; } catch {} - // @ts-expect-error assert(print === Deno.core.print); }); diff --git a/cli/tests/unit/headers_test.ts b/cli/tests/unit/headers_test.ts index 6dd60c8c560b47..84c342fd8fe093 100644 --- a/cli/tests/unit/headers_test.ts +++ b/cli/tests/unit/headers_test.ts @@ -10,6 +10,10 @@ const { // @ts-expect-error TypeScript (as of 3.7) does not support indexing namespaces by symbol } = Deno[Deno.internal]; +unitTest(function headersHasCorrectNameProp(): void { + assertEquals(Headers.name, "Headers"); +}); + // Logic heavily copied from web-platform-tests, make // sure pass mostly header basic test // ref: https://github.com/web-platform-tests/wpt/blob/7c50c216081d6ea3c9afe553ee7b64534020a1b2/fetch/api/headers/headers-basic.html @@ -18,8 +22,8 @@ unitTest(function newHeaderTest(): void { new Headers(undefined); new Headers({}); try { - // @ts-expect-error - new Headers(null); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + new Headers(null as any); } catch (e) { assertEquals( e.message, @@ -32,8 +36,8 @@ const headerDict: Record = { name1: "value1", name2: "value2", name3: "value3", - // @ts-expect-error - name4: undefined, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + name4: undefined as any, "Content-Type": "value4", }; // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -260,17 +264,17 @@ unitTest(function headerParamsShouldThrowTypeError(): void { }); unitTest(function headerParamsArgumentsCheck(): void { - const methodRequireOneParam = ["delete", "get", "has", "forEach"]; + const methodRequireOneParam = ["delete", "get", "has", "forEach"] as const; - const methodRequireTwoParams = ["append", "set"]; + const methodRequireTwoParams = ["append", "set"] as const; methodRequireOneParam.forEach((method): void => { const headers = new Headers(); let hasThrown = 0; let errMsg = ""; try { - // @ts-expect-error - headers[method](); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (headers as any)[method](); hasThrown = 1; } catch (err) { errMsg = err.message; @@ -293,8 +297,8 @@ unitTest(function headerParamsArgumentsCheck(): void { let errMsg = ""; try { - // @ts-expect-error - headers[method](); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (headers as any)[method](); hasThrown = 1; } catch (err) { errMsg = err.message; @@ -313,8 +317,8 @@ unitTest(function headerParamsArgumentsCheck(): void { hasThrown = 0; errMsg = ""; try { - // @ts-expect-error - headers[method]("foo"); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (headers as any)[method]("foo"); hasThrown = 1; } catch (err) { errMsg = err.message; diff --git a/cli/tests/unit/os_test.ts b/cli/tests/unit/os_test.ts index e9900253431c32..72c88b0e10e253 100644 --- a/cli/tests/unit/os_test.ts +++ b/cli/tests/unit/os_test.ts @@ -277,15 +277,13 @@ unitTest({ perms: { read: true } }, function execPath(): void { }); unitTest({ perms: { read: false } }, function execPathPerm(): void { - let caughtError = false; - try { - Deno.execPath(); - } catch (err) { - caughtError = true; - assert(err instanceof Deno.errors.PermissionDenied); - assertEquals(err.name, "PermissionDenied"); - } - assert(caughtError); + assertThrows( + () => { + Deno.execPath(); + }, + Deno.errors.PermissionDenied, + "read access to , run again with the --allow-read flag" + ); }); unitTest({ perms: { env: true } }, function loadavgSuccess(): void { diff --git a/cli/tests/unit/remove_test.ts b/cli/tests/unit/remove_test.ts index 35e5c821e172c3..f9095c1c1da8a3 100644 --- a/cli/tests/unit/remove_test.ts +++ b/cli/tests/unit/remove_test.ts @@ -1,465 +1,284 @@ // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. import { unitTest, assert, assertEquals } from "./test_util.ts"; -// SYNC +const REMOVE_METHODS = ["remove", "removeSync"] as const; unitTest( { perms: { write: true, read: true } }, - function removeSyncDirSuccess(): void { - // REMOVE EMPTY DIRECTORY - const path = Deno.makeTempDirSync() + "/subdir"; - Deno.mkdirSync(path); - const pathInfo = Deno.statSync(path); - assert(pathInfo.isDirectory); // check exist first - Deno.removeSync(path); // remove - // We then check again after remove - let err; - try { - Deno.statSync(path); - } catch (e) { - err = e; - } - // Directory is gone - assert(err instanceof Deno.errors.NotFound); - } -); - -unitTest( - { perms: { write: true, read: true } }, - function removeSyncFileSuccess(): void { - // REMOVE FILE - const enc = new TextEncoder(); - const data = enc.encode("Hello"); - const filename = Deno.makeTempDirSync() + "/test.txt"; - Deno.writeFileSync(filename, data, { mode: 0o666 }); - const fileInfo = Deno.statSync(filename); - assert(fileInfo.isFile); // check exist first - Deno.removeSync(filename); // remove - // We then check again after remove - let err; - try { - Deno.statSync(filename); - } catch (e) { - err = e; + async function removeDirSuccess(): Promise { + for (const method of REMOVE_METHODS) { + // REMOVE EMPTY DIRECTORY + const path = Deno.makeTempDirSync() + "/subdir"; + Deno.mkdirSync(path); + const pathInfo = Deno.statSync(path); + assert(pathInfo.isDirectory); // check exist first + await Deno[method](path); // remove + // We then check again after remove + let err; + try { + Deno.statSync(path); + } catch (e) { + err = e; + } + // Directory is gone + assert(err instanceof Deno.errors.NotFound); } - // File is gone - assert(err instanceof Deno.errors.NotFound); } ); unitTest( { perms: { write: true, read: true } }, - function removeSyncFail(): void { - // NON-EMPTY DIRECTORY - const path = Deno.makeTempDirSync() + "/dir/subdir"; - const subPath = path + "/subsubdir"; - Deno.mkdirSync(path, { recursive: true }); - Deno.mkdirSync(subPath); - const pathInfo = Deno.statSync(path); - assert(pathInfo.isDirectory); // check exist first - const subPathInfo = Deno.statSync(subPath); - assert(subPathInfo.isDirectory); // check exist first - let err; - try { - // Should not be able to recursively remove - Deno.removeSync(path); - } catch (e) { - err = e; - } - // TODO(ry) Is Other really the error we should get here? What would Go do? - assert(err instanceof Error); - // NON-EXISTENT DIRECTORY/FILE - try { - // Non-existent - Deno.removeSync("/baddir"); - } catch (e) { - err = e; + async function removeFileSuccess(): Promise { + for (const method of REMOVE_METHODS) { + // REMOVE FILE + const enc = new TextEncoder(); + const data = enc.encode("Hello"); + const filename = Deno.makeTempDirSync() + "/test.txt"; + Deno.writeFileSync(filename, data, { mode: 0o666 }); + const fileInfo = Deno.statSync(filename); + assert(fileInfo.isFile); // check exist first + await Deno[method](filename); // remove + // We then check again after remove + let err; + try { + Deno.statSync(filename); + } catch (e) { + err = e; + } + // File is gone + assert(err instanceof Deno.errors.NotFound); } - assert(err instanceof Deno.errors.NotFound); } ); unitTest( { perms: { write: true, read: true } }, - function removeSyncDanglingSymlinkSuccess(): void { - const danglingSymlinkPath = Deno.makeTempDirSync() + "/dangling_symlink"; - if (Deno.build.os === "windows") { - Deno.symlinkSync("unexistent_file", danglingSymlinkPath, { - type: "file", - }); - } else { - Deno.symlinkSync("unexistent_file", danglingSymlinkPath); - } - const pathInfo = Deno.lstatSync(danglingSymlinkPath); - assert(pathInfo.isSymlink); - Deno.removeSync(danglingSymlinkPath); - let err; - try { - Deno.lstatSync(danglingSymlinkPath); - } catch (e) { - err = e; + async function removeFail(): Promise { + for (const method of REMOVE_METHODS) { + // NON-EMPTY DIRECTORY + const path = Deno.makeTempDirSync() + "/dir/subdir"; + const subPath = path + "/subsubdir"; + Deno.mkdirSync(path, { recursive: true }); + Deno.mkdirSync(subPath); + const pathInfo = Deno.statSync(path); + assert(pathInfo.isDirectory); // check exist first + const subPathInfo = Deno.statSync(subPath); + assert(subPathInfo.isDirectory); // check exist first + let err; + try { + // Should not be able to recursively remove + await Deno[method](path); + } catch (e) { + err = e; + } + // TODO(ry) Is Other really the error we should get here? What would Go do? + assert(err instanceof Error); + // NON-EXISTENT DIRECTORY/FILE + try { + // Non-existent + await Deno[method]("/baddir"); + } catch (e) { + err = e; + } + assert(err instanceof Deno.errors.NotFound); } - assert(err instanceof Deno.errors.NotFound); } ); unitTest( { perms: { write: true, read: true } }, - function removeSyncValidSymlinkSuccess(): void { - const encoder = new TextEncoder(); - const data = encoder.encode("Test"); - const tempDir = Deno.makeTempDirSync(); - const filePath = tempDir + "/test.txt"; - const validSymlinkPath = tempDir + "/valid_symlink"; - Deno.writeFileSync(filePath, data, { mode: 0o666 }); - if (Deno.build.os === "windows") { - Deno.symlinkSync(filePath, validSymlinkPath, { type: "file" }); - } else { - Deno.symlinkSync(filePath, validSymlinkPath); - } - const symlinkPathInfo = Deno.statSync(validSymlinkPath); - assert(symlinkPathInfo.isFile); - Deno.removeSync(validSymlinkPath); - let err; - try { - Deno.statSync(validSymlinkPath); - } catch (e) { - err = e; + async function removeDanglingSymlinkSuccess(): Promise { + for (const method of REMOVE_METHODS) { + const danglingSymlinkPath = Deno.makeTempDirSync() + "/dangling_symlink"; + if (Deno.build.os === "windows") { + Deno.symlinkSync("unexistent_file", danglingSymlinkPath, { + type: "file", + }); + } else { + Deno.symlinkSync("unexistent_file", danglingSymlinkPath); + } + const pathInfo = Deno.lstatSync(danglingSymlinkPath); + assert(pathInfo.isSymlink); + await Deno[method](danglingSymlinkPath); + let err; + try { + Deno.lstatSync(danglingSymlinkPath); + } catch (e) { + err = e; + } + assert(err instanceof Deno.errors.NotFound); } - Deno.removeSync(filePath); - assert(err instanceof Deno.errors.NotFound); } ); -unitTest({ perms: { write: false } }, function removeSyncPerm(): void { - let err; - try { - Deno.removeSync("/baddir"); - } catch (e) { - err = e; - } - assert(err instanceof Deno.errors.PermissionDenied); - assertEquals(err.name, "PermissionDenied"); -}); - unitTest( { perms: { write: true, read: true } }, - function removeAllSyncDirSuccess(): void { - // REMOVE EMPTY DIRECTORY - let path = Deno.makeTempDirSync() + "/dir/subdir"; - Deno.mkdirSync(path, { recursive: true }); - let pathInfo = Deno.statSync(path); - assert(pathInfo.isDirectory); // check exist first - Deno.removeSync(path, { recursive: true }); // remove - // We then check again after remove - let err; - try { - Deno.statSync(path); - } catch (e) { - err = e; - } - // Directory is gone - assert(err instanceof Deno.errors.NotFound); - - // REMOVE NON-EMPTY DIRECTORY - path = Deno.makeTempDirSync() + "/dir/subdir"; - const subPath = path + "/subsubdir"; - Deno.mkdirSync(path, { recursive: true }); - Deno.mkdirSync(subPath); - pathInfo = Deno.statSync(path); - assert(pathInfo.isDirectory); // check exist first - const subPathInfo = Deno.statSync(subPath); - assert(subPathInfo.isDirectory); // check exist first - Deno.removeSync(path, { recursive: true }); // remove - // We then check parent directory again after remove - try { - Deno.statSync(path); - } catch (e) { - err = e; + async function removeValidSymlinkSuccess(): Promise { + for (const method of REMOVE_METHODS) { + const encoder = new TextEncoder(); + const data = encoder.encode("Test"); + const tempDir = Deno.makeTempDirSync(); + const filePath = tempDir + "/test.txt"; + const validSymlinkPath = tempDir + "/valid_symlink"; + Deno.writeFileSync(filePath, data, { mode: 0o666 }); + if (Deno.build.os === "windows") { + Deno.symlinkSync(filePath, validSymlinkPath, { type: "file" }); + } else { + Deno.symlinkSync(filePath, validSymlinkPath); + } + const symlinkPathInfo = Deno.statSync(validSymlinkPath); + assert(symlinkPathInfo.isFile); + await Deno[method](validSymlinkPath); + let err; + try { + Deno.statSync(validSymlinkPath); + } catch (e) { + err = e; + } + await Deno[method](filePath); + assert(err instanceof Deno.errors.NotFound); } - // Directory is gone - assert(err instanceof Deno.errors.NotFound); } ); -unitTest( - { perms: { write: true, read: true } }, - function removeAllSyncFileSuccess(): void { - // REMOVE FILE - const enc = new TextEncoder(); - const data = enc.encode("Hello"); - const filename = Deno.makeTempDirSync() + "/test.txt"; - Deno.writeFileSync(filename, data, { mode: 0o666 }); - const fileInfo = Deno.statSync(filename); - assert(fileInfo.isFile); // check exist first - Deno.removeSync(filename, { recursive: true }); // remove - // We then check again after remove +unitTest({ perms: { write: false } }, async function removePerm(): Promise< + void +> { + for (const method of REMOVE_METHODS) { let err; try { - Deno.statSync(filename); + await Deno[method]("/baddir"); } catch (e) { err = e; } - // File is gone - assert(err instanceof Deno.errors.NotFound); - } -); - -unitTest({ perms: { write: true } }, function removeAllSyncFail(): void { - // NON-EXISTENT DIRECTORY/FILE - let err; - try { - // Non-existent - Deno.removeSync("/baddir", { recursive: true }); - } catch (e) { - err = e; - } - assert(err instanceof Deno.errors.NotFound); -}); - -unitTest({ perms: { write: false } }, function removeAllSyncPerm(): void { - let err; - try { - Deno.removeSync("/baddir", { recursive: true }); - } catch (e) { - err = e; + assert(err instanceof Deno.errors.PermissionDenied); + assertEquals(err.name, "PermissionDenied"); } - assert(err instanceof Deno.errors.PermissionDenied); - assertEquals(err.name, "PermissionDenied"); }); -// ASYNC - -unitTest( - { perms: { write: true, read: true } }, - async function removeDirSuccess(): Promise { - // REMOVE EMPTY DIRECTORY - const path = Deno.makeTempDirSync() + "/dir/subdir"; - Deno.mkdirSync(path, { recursive: true }); - const pathInfo = Deno.statSync(path); - assert(pathInfo.isDirectory); // check exist first - await Deno.remove(path); // remove - // We then check again after remove - let err; - try { - Deno.statSync(path); - } catch (e) { - err = e; - } - // Directory is gone - assert(err instanceof Deno.errors.NotFound); - } -); - unitTest( { perms: { write: true, read: true } }, - async function removeFileSuccess(): Promise { - // REMOVE FILE - const enc = new TextEncoder(); - const data = enc.encode("Hello"); - const filename = Deno.makeTempDirSync() + "/test.txt"; - Deno.writeFileSync(filename, data, { mode: 0o666 }); - const fileInfo = Deno.statSync(filename); - assert(fileInfo.isFile); // check exist first - await Deno.remove(filename); // remove - // We then check again after remove - let err; - try { - Deno.statSync(filename); - } catch (e) { - err = e; - } - // File is gone - assert(err instanceof Deno.errors.NotFound); - } -); + async function removeAllDirSuccess(): Promise { + for (const method of REMOVE_METHODS) { + // REMOVE EMPTY DIRECTORY + let path = Deno.makeTempDirSync() + "/dir/subdir"; + Deno.mkdirSync(path, { recursive: true }); + let pathInfo = Deno.statSync(path); + assert(pathInfo.isDirectory); // check exist first + await Deno[method](path, { recursive: true }); // remove + // We then check again after remove + let err; + try { + Deno.statSync(path); + } catch (e) { + err = e; + } + // Directory is gone + assert(err instanceof Deno.errors.NotFound); -unitTest( - { perms: { write: true, read: true } }, - async function removeFail(): Promise { - // NON-EMPTY DIRECTORY - const path = Deno.makeTempDirSync() + "/dir/subdir"; - const subPath = path + "/subsubdir"; - Deno.mkdirSync(path, { recursive: true }); - Deno.mkdirSync(subPath); - const pathInfo = Deno.statSync(path); - assert(pathInfo.isDirectory); // check exist first - const subPathInfo = Deno.statSync(subPath); - assert(subPathInfo.isDirectory); // check exist first - let err; - try { - // Should not be able to recursively remove - await Deno.remove(path); - } catch (e) { - err = e; - } - assert(err instanceof Error); - // NON-EXISTENT DIRECTORY/FILE - try { - // Non-existent - await Deno.remove("/baddir"); - } catch (e) { - err = e; + // REMOVE NON-EMPTY DIRECTORY + path = Deno.makeTempDirSync() + "/dir/subdir"; + const subPath = path + "/subsubdir"; + Deno.mkdirSync(path, { recursive: true }); + Deno.mkdirSync(subPath); + pathInfo = Deno.statSync(path); + assert(pathInfo.isDirectory); // check exist first + const subPathInfo = Deno.statSync(subPath); + assert(subPathInfo.isDirectory); // check exist first + await Deno[method](path, { recursive: true }); // remove + // We then check parent directory again after remove + try { + Deno.statSync(path); + } catch (e) { + err = e; + } + // Directory is gone + assert(err instanceof Deno.errors.NotFound); } - assert(err instanceof Deno.errors.NotFound); } ); unitTest( { perms: { write: true, read: true } }, - async function removeDanglingSymlinkSuccess(): Promise { - const danglingSymlinkPath = Deno.makeTempDirSync() + "/dangling_symlink"; - if (Deno.build.os === "windows") { - Deno.symlinkSync("unexistent_file", danglingSymlinkPath, { - type: "file", - }); - } else { - Deno.symlinkSync("unexistent_file", danglingSymlinkPath); - } - const pathInfo = Deno.lstatSync(danglingSymlinkPath); - assert(pathInfo.isSymlink); - await Deno.remove(danglingSymlinkPath); - let err; - try { - Deno.lstatSync(danglingSymlinkPath); - } catch (e) { - err = e; + async function removeAllFileSuccess(): Promise { + for (const method of REMOVE_METHODS) { + // REMOVE FILE + const enc = new TextEncoder(); + const data = enc.encode("Hello"); + const filename = Deno.makeTempDirSync() + "/test.txt"; + Deno.writeFileSync(filename, data, { mode: 0o666 }); + const fileInfo = Deno.statSync(filename); + assert(fileInfo.isFile); // check exist first + await Deno[method](filename, { recursive: true }); // remove + // We then check again after remove + let err; + try { + Deno.statSync(filename); + } catch (e) { + err = e; + } + // File is gone + assert(err instanceof Deno.errors.NotFound); } - assert(err instanceof Deno.errors.NotFound); } ); -unitTest( - { perms: { write: true, read: true } }, - async function removeValidSymlinkSuccess(): Promise { - const encoder = new TextEncoder(); - const data = encoder.encode("Test"); - const tempDir = Deno.makeTempDirSync(); - const filePath = tempDir + "/test.txt"; - const validSymlinkPath = tempDir + "/valid_symlink"; - Deno.writeFileSync(filePath, data, { mode: 0o666 }); - if (Deno.build.os === "windows") { - Deno.symlinkSync(filePath, validSymlinkPath, { type: "file" }); - } else { - Deno.symlinkSync(filePath, validSymlinkPath); - } - const symlinkPathInfo = Deno.statSync(validSymlinkPath); - assert(symlinkPathInfo.isFile); - await Deno.remove(validSymlinkPath); +unitTest({ perms: { write: true } }, async function removeAllFail(): Promise< + void +> { + for (const method of REMOVE_METHODS) { + // NON-EXISTENT DIRECTORY/FILE let err; try { - Deno.statSync(validSymlinkPath); + // Non-existent + await Deno[method]("/baddir", { recursive: true }); } catch (e) { err = e; } - Deno.removeSync(filePath); assert(err instanceof Deno.errors.NotFound); } -); +}); -unitTest({ perms: { write: false } }, async function removePerm(): Promise< +unitTest({ perms: { write: false } }, async function removeAllPerm(): Promise< void > { - let err; - try { - await Deno.remove("/baddir"); - } catch (e) { - err = e; - } - assert(err instanceof Deno.errors.PermissionDenied); - assertEquals(err.name, "PermissionDenied"); -}); - -unitTest( - { perms: { write: true, read: true } }, - async function removeAllDirSuccess(): Promise { - // REMOVE EMPTY DIRECTORY - let path = Deno.makeTempDirSync() + "/dir/subdir"; - Deno.mkdirSync(path, { recursive: true }); - let pathInfo = Deno.statSync(path); - assert(pathInfo.isDirectory); // check exist first - await Deno.remove(path, { recursive: true }); // remove - // We then check again after remove + for (const method of REMOVE_METHODS) { let err; try { - Deno.statSync(path); + await Deno[method]("/baddir", { recursive: true }); } catch (e) { err = e; } - // Directory is gone - assert(err instanceof Deno.errors.NotFound); - - // REMOVE NON-EMPTY DIRECTORY - path = Deno.makeTempDirSync() + "/dir/subdir"; - const subPath = path + "/subsubdir"; - Deno.mkdirSync(path, { recursive: true }); - Deno.mkdirSync(subPath); - pathInfo = Deno.statSync(path); - assert(pathInfo.isDirectory); // check exist first - const subPathInfo = Deno.statSync(subPath); - assert(subPathInfo.isDirectory); // check exist first - await Deno.remove(path, { recursive: true }); // remove - // We then check parent directory again after remove - try { - Deno.statSync(path); - } catch (e) { - err = e; - } - // Directory is gone - assert(err instanceof Deno.errors.NotFound); + assert(err instanceof Deno.errors.PermissionDenied); + assertEquals(err.name, "PermissionDenied"); } -); +}); unitTest( - { perms: { write: true, read: true } }, - async function removeAllFileSuccess(): Promise { - // REMOVE FILE - const enc = new TextEncoder(); - const data = enc.encode("Hello"); - const filename = Deno.makeTempDirSync() + "/test.txt"; - Deno.writeFileSync(filename, data, { mode: 0o666 }); - const fileInfo = Deno.statSync(filename); - assert(fileInfo.isFile); // check exist first - await Deno.remove(filename, { recursive: true }); // remove - // We then check again after remove - let err; - try { - Deno.statSync(filename); - } catch (e) { - err = e; + { + ignore: Deno.build.os === "windows", + perms: { write: true, read: true }, + }, + async function removeUnixSocketSuccess(): Promise { + for (const method of REMOVE_METHODS) { + // MAKE TEMPORARY UNIX SOCKET + const path = Deno.makeTempDirSync() + "/test.sock"; + const listener = Deno.listen({ transport: "unix", path }); + listener.close(); + Deno.statSync(path); // check if unix socket exists + + await Deno[method](path); + let err; + try { + Deno.statSync(path); + } catch (e) { + err = e; + } + assert(err instanceof Deno.errors.NotFound); } - // File is gone - assert(err instanceof Deno.errors.NotFound); } ); -unitTest({ perms: { write: true } }, async function removeAllFail(): Promise< - void -> { - // NON-EXISTENT DIRECTORY/FILE - let err; - try { - // Non-existent - await Deno.remove("/baddir", { recursive: true }); - } catch (e) { - err = e; - } - assert(err instanceof Deno.errors.NotFound); -}); - -unitTest({ perms: { write: false } }, async function removeAllPerm(): Promise< - void -> { - let err; - try { - await Deno.remove("/baddir", { recursive: true }); - } catch (e) { - err = e; - } - assert(err instanceof Deno.errors.PermissionDenied); - assertEquals(err.name, "PermissionDenied"); -}); - if (Deno.build.os === "windows") { unitTest( { perms: { run: true, write: true, read: true } }, diff --git a/cli/tests/unit/request_test.ts b/cli/tests/unit/request_test.ts index be6e956b771fd5..24f5b6d823b71c 100644 --- a/cli/tests/unit/request_test.ts +++ b/cli/tests/unit/request_test.ts @@ -10,22 +10,22 @@ unitTest(function fromInit(): void { }, }); - // @ts-expect-error - assertEquals("ahoyhoy", req._bodySource); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + assertEquals("ahoyhoy", (req as any)._bodySource); assertEquals(req.url, "https://example.com"); assertEquals(req.headers.get("test-header"), "value"); }); unitTest(function fromRequest(): void { const r = new Request("https://example.com"); - // @ts-expect-error - r._bodySource = "ahoyhoy"; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (r as any)._bodySource = "ahoyhoy"; r.headers.set("test-header", "value"); const req = new Request(r); - // @ts-expect-error - assertEquals(req._bodySource, r._bodySource); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + assertEquals((req as any)._bodySource, (r as any)._bodySource); assertEquals(req.url, r.url); assertEquals(req.headers.get("test-header"), r.headers.get("test-header")); }); @@ -44,6 +44,6 @@ unitTest(async function cloneRequestBodyStream(): Promise { assertEquals(b1, b2); - // @ts-expect-error - assert(r1._bodySource !== r2._bodySource); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + assert((r1 as any)._bodySource !== (r2 as any)._bodySource); }); diff --git a/cli/tests/unit/streams_internal_test.ts b/cli/tests/unit/streams_internal_test.ts index f49c9f494edd8b..346ec27af50dc5 100644 --- a/cli/tests/unit/streams_internal_test.ts +++ b/cli/tests/unit/streams_internal_test.ts @@ -2,15 +2,12 @@ import { unitTest, assertThrows } from "./test_util.ts"; unitTest(function streamReadableHwmError() { - const invalidHwm = [NaN, Number("NaN"), {}, -1, "two"]; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const invalidHwm: any[] = [NaN, Number("NaN"), {}, -1, "two"]; for (const highWaterMark of invalidHwm) { assertThrows( () => { - new ReadableStream( - undefined, - // @ts-expect-error - { highWaterMark } - ); + new ReadableStream(undefined, { highWaterMark }); }, RangeError, "highWaterMark must be a positive number or Infinity. Received:" @@ -20,20 +17,20 @@ unitTest(function streamReadableHwmError() { assertThrows(() => { new ReadableStream( undefined, - // @ts-expect-error - { highWaterMark: Symbol("hwk") } + // eslint-disable-next-line @typescript-eslint/no-explicit-any + { highWaterMark: Symbol("hwk") as any } ); }, TypeError); }); unitTest(function streamWriteableHwmError() { - const invalidHwm = [NaN, Number("NaN"), {}, -1, "two"]; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const invalidHwm: any[] = [NaN, Number("NaN"), {}, -1, "two"]; for (const highWaterMark of invalidHwm) { assertThrows( () => { new WritableStream( undefined, - // @ts-expect-error new CountQueuingStrategy({ highWaterMark }) ); }, @@ -45,23 +42,19 @@ unitTest(function streamWriteableHwmError() { assertThrows(() => { new WritableStream( undefined, - // @ts-expect-error - new CountQueuingStrategy({ highWaterMark: Symbol("hwmk") }) + // eslint-disable-next-line @typescript-eslint/no-explicit-any + new CountQueuingStrategy({ highWaterMark: Symbol("hwmk") as any }) ); }, TypeError); }); unitTest(function streamTransformHwmError() { - const invalidHwm = [NaN, Number("NaN"), {}, -1, "two"]; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const invalidHwm: any[] = [NaN, Number("NaN"), {}, -1, "two"]; for (const highWaterMark of invalidHwm) { assertThrows( () => { - new TransformStream( - undefined, - undefined, - // @ts-expect-error - { highWaterMark } - ); + new TransformStream(undefined, undefined, { highWaterMark }); }, RangeError, "highWaterMark must be a positive number or Infinity. Received:" @@ -72,8 +65,8 @@ unitTest(function streamTransformHwmError() { new TransformStream( undefined, undefined, - // @ts-expect-error - { highWaterMark: Symbol("hwmk") } + // eslint-disable-next-line @typescript-eslint/no-explicit-any + { highWaterMark: Symbol("hwmk") as any } ); }, TypeError); }); diff --git a/cli/tests/unit/url_search_params_test.ts b/cli/tests/unit/url_search_params_test.ts index ce55a752057b6d..227deeda7a48a7 100644 --- a/cli/tests/unit/url_search_params_test.ts +++ b/cli/tests/unit/url_search_params_test.ts @@ -177,8 +177,8 @@ unitTest(function urlSearchParamsAppendArgumentsCheck(): void { const searchParams = new URLSearchParams(); let hasThrown = 0; try { - // @ts-expect-error - searchParams[method](); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (searchParams as any)[method](); hasThrown = 1; } catch (err) { if (err instanceof TypeError) { @@ -194,8 +194,8 @@ unitTest(function urlSearchParamsAppendArgumentsCheck(): void { const searchParams = new URLSearchParams(); let hasThrown = 0; try { - // @ts-expect-error - searchParams[method]("foo"); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (searchParams as any)[method]("foo"); hasThrown = 1; } catch (err) { if (err instanceof TypeError) { @@ -235,8 +235,10 @@ unitTest(function urlSearchParamsCustomSymbolIterator(): void { unitTest( function urlSearchParamsCustomSymbolIteratorWithNonStringParams(): void { const params = {}; - // @ts-expect-error - params[Symbol.iterator] = function* (): IterableIterator<[number, number]> { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (params as any)[Symbol.iterator] = function* (): IterableIterator< + [number, number] + > { yield [1, 2]; }; const params1 = new URLSearchParams((params as unknown) as string[][]); diff --git a/cli/tests/unstable_disabled.out b/cli/tests/unstable_disabled.out index 695ca122cec46a..e951c5d0abd192 100644 --- a/cli/tests/unstable_disabled.out +++ b/cli/tests/unstable_disabled.out @@ -1,5 +1,5 @@ [WILDCARD] -error: TS2339 [ERROR]: Property 'loadavg' does not exist on type 'typeof Deno'. +error: TS2339 [ERROR]: Property 'loadavg' does not exist on type 'typeof Deno'. 'Deno.loadavg' is an unstable API. Did you forget to run with the '--unstable' flag? console.log(Deno.loadavg); ~~~~~~~ at [WILDCARD]/cli/tests/unstable.ts:1:18 diff --git a/cli/tsc.rs b/cli/tsc.rs index 664721bc15135b..1b3208e7534965 100644 --- a/cli/tsc.rs +++ b/cli/tsc.rs @@ -5,10 +5,9 @@ use crate::diagnostics::DiagnosticItem; use crate::disk_cache::DiskCache; use crate::file_fetcher::SourceFile; use crate::file_fetcher::SourceFileFetcher; -use crate::fmt; -use crate::fs as deno_fs; use crate::global_state::GlobalState; use crate::import_map::ImportMap; +use crate::module_graph::ModuleGraphFile; use crate::module_graph::ModuleGraphLoader; use crate::msg; use crate::op_error::OpError; @@ -16,7 +15,6 @@ use crate::ops; use crate::permissions::Permissions; use crate::source_maps::SourceMapGetter; use crate::startup_data; -use crate::state::exit_unstable; use crate::state::State; use crate::version; use crate::web_worker::WebWorker; @@ -50,73 +48,69 @@ use std::sync::atomic::Ordering; use std::sync::Arc; use std::sync::Mutex; use std::task::Poll; -use std::time::Instant; use url::Url; -// TODO(bartlomieju): make static -pub fn get_available_libs() -> Vec { - vec![ - "deno.ns".to_string(), - "deno.window".to_string(), - "deno.worker".to_string(), - "deno.shared_globals".to_string(), - "deno.unstable".to_string(), - "dom".to_string(), - "dom.iterable".to_string(), - "es5".to_string(), - "es6".to_string(), - "esnext".to_string(), - "es2020".to_string(), - "es2020.full".to_string(), - "es2019".to_string(), - "es2019.full".to_string(), - "es2018".to_string(), - "es2018.full".to_string(), - "es2017".to_string(), - "es2017.full".to_string(), - "es2016".to_string(), - "es2016.full".to_string(), - "es2015".to_string(), - "es2015.collection".to_string(), - "es2015.core".to_string(), - "es2015.generator".to_string(), - "es2015.iterable".to_string(), - "es2015.promise".to_string(), - "es2015.proxy".to_string(), - "es2015.reflect".to_string(), - "es2015.symbol".to_string(), - "es2015.symbol.wellknown".to_string(), - "es2016.array.include".to_string(), - "es2017.intl".to_string(), - "es2017.object".to_string(), - "es2017.sharedmemory".to_string(), - "es2017.string".to_string(), - "es2017.typedarrays".to_string(), - "es2018.asyncgenerator".to_string(), - "es2018.asynciterable".to_string(), - "es2018.intl".to_string(), - "es2018.promise".to_string(), - "es2018.regexp".to_string(), - "es2019.array".to_string(), - "es2019.object".to_string(), - "es2019.string".to_string(), - "es2019.symbol".to_string(), - "es2020.bigint".to_string(), - "es2020.promise".to_string(), - "es2020.string".to_string(), - "es2020.symbol.wellknown".to_string(), - "esnext.array".to_string(), - "esnext.asynciterable".to_string(), - "esnext.bigint".to_string(), - "esnext.intl".to_string(), - "esnext.promise".to_string(), - "esnext.string".to_string(), - "esnext.symbol".to_string(), - "scripthost".to_string(), - "webworker".to_string(), - "webworker.importscripts".to_string(), - ] -} +pub const AVAILABLE_LIBS: &[&str] = &[ + "deno.ns", + "deno.window", + "deno.worker", + "deno.shared_globals", + "deno.unstable", + "dom", + "dom.iterable", + "es5", + "es6", + "esnext", + "es2020", + "es2020.full", + "es2019", + "es2019.full", + "es2018", + "es2018.full", + "es2017", + "es2017.full", + "es2016", + "es2016.full", + "es2015", + "es2015.collection", + "es2015.core", + "es2015.generator", + "es2015.iterable", + "es2015.promise", + "es2015.proxy", + "es2015.reflect", + "es2015.symbol", + "es2015.symbol.wellknown", + "es2016.array.include", + "es2017.intl", + "es2017.object", + "es2017.sharedmemory", + "es2017.string", + "es2017.typedarrays", + "es2018.asyncgenerator", + "es2018.asynciterable", + "es2018.intl", + "es2018.promise", + "es2018.regexp", + "es2019.array", + "es2019.object", + "es2019.string", + "es2019.symbol", + "es2020.bigint", + "es2020.promise", + "es2020.string", + "es2020.symbol.wellknown", + "esnext.array", + "esnext.asynciterable", + "esnext.bigint", + "esnext.intl", + "esnext.promise", + "esnext.string", + "esnext.symbol", + "scripthost", + "webworker", + "webworker.importscripts", +]; #[derive(Debug, Clone)] pub struct CompiledModule { @@ -160,6 +154,7 @@ impl Future for CompilerWorker { } } +// TODO(bartlomieju): use JSONC parser from dprint instead of Regex lazy_static! { static ref CHECK_JS_RE: Regex = Regex::new(r#""checkJs"\s*?:\s*?true"#).unwrap(); @@ -175,9 +170,14 @@ fn create_compiler_worker( // like 'eval', 'repl' let entry_point = ModuleSpecifier::resolve_url_or_path("./__$deno$ts_compiler.ts").unwrap(); - let worker_state = - State::new(global_state.clone(), Some(permissions), entry_point, true) - .expect("Unable to create worker state"); + let worker_state = State::new( + global_state.clone(), + Some(permissions), + entry_point, + None, + true, + ) + .expect("Unable to create worker state"); // TODO(bartlomieju): this metric is never used anywhere // Count how many times we start the compiler worker. @@ -294,6 +294,28 @@ impl CompiledFileMetadata { } } +/// Information associated with compilation of a "module graph", +/// ie. entry point and all its dependencies. +/// It's used to perform cache invalidation if content of any +/// dependency changes. +#[derive(Deserialize, Serialize)] +pub struct GraphFileMetadata { + pub deps: Vec, + pub version_hash: String, +} + +impl GraphFileMetadata { + pub fn from_json_string( + metadata_string: String, + ) -> Result { + serde_json::from_str::(&metadata_string) + } + + pub fn to_json_string(&self) -> Result { + serde_json::to_string(self) + } +} + /// Emit a SHA256 hash based on source code, deno version and TS config. /// Used to check if a recompilation for source code is needed. pub fn source_code_version_hash( @@ -383,6 +405,7 @@ impl TsCompiler { }))) } + // TODO(bartlomieju): this method is no longer needed /// Mark given module URL as compiled to avoid multiple compilations of same /// module in single run. fn mark_compiled(&self, url: &Url) { @@ -390,11 +413,34 @@ impl TsCompiler { c.insert(url.clone()); } - /// Check if given module URL has already been compiled and can be fetched - /// directly from disk. - fn has_compiled(&self, url: &Url) -> bool { - let c = self.compiled.lock().unwrap(); - c.contains(url) + /// Check if there is compiled source in cache that is valid + /// and can be used again. + // TODO(bartlomieju): there should be check that cached file actually exists + fn has_compiled_source( + &self, + file_fetcher: &SourceFileFetcher, + url: &Url, + ) -> bool { + let specifier = ModuleSpecifier::from(url.clone()); + if let Some(source_file) = file_fetcher + .fetch_cached_source_file(&specifier, Permissions::allow_all()) + { + if let Some(metadata) = self.get_metadata(&url) { + // 2. compare version hashes + // TODO: it would probably be good idea to make it method implemented on SourceFile + let version_hash_to_validate = source_code_version_hash( + &source_file.source_code, + version::DENO, + &self.config.hash, + ); + + if metadata.version_hash == version_hash_to_validate { + return true; + } + } + } + + false } /// Asynchronously compile module and all it's dependencies. @@ -406,64 +452,43 @@ impl TsCompiler { /// /// If compilation is required then new V8 worker is spawned with fresh TS /// compiler. - pub async fn compile( + pub async fn compile_module_graph( &self, global_state: GlobalState, source_file: &SourceFile, target: TargetLib, permissions: Permissions, - is_dyn_import: bool, - ) -> Result { - if self.has_compiled(&source_file.url) { - return self.get_compiled_module(&source_file.url); - } + module_graph: HashMap, + ) -> Result<(), ErrBox> { + let mut has_cached_version = false; if self.use_disk_cache { - // Try to load cached version: - // 1. check if there's 'meta' file - if let Some(metadata) = self.get_metadata(&source_file.url) { - // 2. compare version hashes - // TODO: it would probably be good idea to make it method implemented on SourceFile - let version_hash_to_validate = source_code_version_hash( - &source_file.source_code, - version::DENO, + if let Some(metadata) = self.get_graph_metadata(&source_file.url) { + has_cached_version = true; + + let version_hash = crate::checksum::gen(vec![ + version::DENO.as_bytes(), &self.config.hash, - ); + ]); - if metadata.version_hash == version_hash_to_validate { - debug!("load_cache metadata version hash match"); - if let Ok(compiled_module) = - self.get_compiled_module(&source_file.url) - { - self.mark_compiled(&source_file.url); - return Ok(compiled_module); - } + has_cached_version &= metadata.version_hash == version_hash; + has_cached_version &= self + .has_compiled_source(&global_state.file_fetcher, &source_file.url); + + for dep in metadata.deps { + let url = Url::parse(&dep).expect("Dep is not a valid url"); + has_cached_version &= + self.has_compiled_source(&global_state.file_fetcher, &url); } } } - let source_file_ = source_file.clone(); + + if has_cached_version { + return Ok(()); + } + let module_url = source_file.url.clone(); - let module_specifier = ModuleSpecifier::from(source_file.url.clone()); - let import_map: Option = - match global_state.flags.import_map_path.as_ref() { - None => None, - Some(file_path) => { - if !global_state.flags.unstable { - exit_unstable("--importmap") - } - Some(ImportMap::load(file_path)?) - } - }; - let mut module_graph_loader = ModuleGraphLoader::new( - global_state.file_fetcher.clone(), - import_map, - permissions.clone(), - is_dyn_import, - true, - ); - module_graph_loader.add_to_graph(&module_specifier).await?; - let module_graph = module_graph_loader.get_graph(); let module_graph_json = serde_json::to_value(module_graph).expect("Failed to serialize data"); let target = match target { @@ -500,23 +525,17 @@ impl TsCompiler { let req_msg = j.to_string().into_boxed_str().into_boxed_bytes(); - let ts_compiler = self.clone(); - + // TODO(bartlomieju): lift this call up - TSC shouldn't print anything info!( "{} {}", colors::green("Compile".to_string()), module_url.to_string() ); - let start = Instant::now(); - let msg = execute_in_same_thread(global_state.clone(), permissions, req_msg) .await?; - let end = Instant::now(); - debug!("time spent in compiler thread {:#?}", end - start); - let json_str = std::str::from_utf8(&msg).unwrap(); let compile_response: CompileResponse = serde_json::from_str(json_str)?; @@ -525,8 +544,69 @@ impl TsCompiler { return Err(ErrBox::from(compile_response.diagnostics)); } + self.set_graph_metadata( + source_file.url.clone(), + &compile_response.emit_map, + )?; self.cache_emitted_files(compile_response.emit_map)?; - ts_compiler.get_compiled_module(&source_file_.url) + Ok(()) + } + + fn get_graph_metadata(&self, url: &Url) -> Option { + // Try to load cached version: + // 1. check if there's 'meta' file + let cache_key = self + .disk_cache + .get_cache_filename_with_extension(url, "graph"); + if let Ok(metadata_bytes) = self.disk_cache.get(&cache_key) { + if let Ok(metadata) = std::str::from_utf8(&metadata_bytes) { + if let Ok(read_metadata) = + GraphFileMetadata::from_json_string(metadata.to_string()) + { + return Some(read_metadata); + } + } + } + + None + } + + fn set_graph_metadata( + &self, + url: Url, + emit_map: &HashMap, + ) -> std::io::Result<()> { + let version_hash = + crate::checksum::gen(vec![version::DENO.as_bytes(), &self.config.hash]); + let mut deps = vec![]; + + for (_emitted_name, source) in emit_map.iter() { + let specifier = ModuleSpecifier::resolve_url(&source.filename) + .expect("Should be a valid module specifier"); + + let source_file = self + .file_fetcher + .fetch_cached_source_file(&specifier, Permissions::allow_all()) + .expect("Source file not found"); + + // NOTE: JavaScript files are only cached to disk if `checkJs` + // option in on + if source_file.media_type == msg::MediaType::JavaScript + && !self.compile_js + { + continue; + } + + deps.push(specifier.to_string()); + } + + let graph_metadata = GraphFileMetadata { deps, version_hash }; + let meta_key = self + .disk_cache + .get_cache_filename_with_extension(&url, "graph"); + self + .disk_cache + .set(&meta_key, graph_metadata.to_json_string()?.as_bytes()) } /// Get associated `CompiledFileMetadata` for given module if it exists. @@ -557,10 +637,23 @@ impl TsCompiler { let specifier = ModuleSpecifier::resolve_url(&source.filename) .expect("Should be a valid module specifier"); + let source_file = self + .file_fetcher + .fetch_cached_source_file(&specifier, Permissions::allow_all()) + .expect("Source file not found"); + + // NOTE: JavaScript files are only cached to disk if `checkJs` + // option in on + if source_file.media_type == msg::MediaType::JavaScript + && !self.compile_js + { + continue; + } + if emitted_name.ends_with(".map") { self.cache_source_map(&specifier, &source.contents)?; } else if emitted_name.ends_with(".js") { - self.cache_compiled_file(&specifier, &source.contents)?; + self.cache_compiled_file(&specifier, source_file, &source.contents)?; } else { panic!("Trying to cache unknown file type {}", emitted_name); } @@ -618,20 +711,9 @@ impl TsCompiler { fn cache_compiled_file( &self, module_specifier: &ModuleSpecifier, + source_file: SourceFile, contents: &str, ) -> std::io::Result<()> { - let source_file = self - .file_fetcher - .fetch_cached_source_file(&module_specifier, Permissions::allow_all()) - .expect("Source file not found"); - - // NOTE: JavaScript files are only cached to disk if `checkJs` - // option in on - if source_file.media_type == msg::MediaType::JavaScript && !self.compile_js - { - return Ok(()); - } - // By default TSC output source map url that is relative; we need // to substitute it manually to correct file URL in DENO_DIR. let mut content_lines = contents @@ -664,10 +746,6 @@ impl TsCompiler { .get_cache_filename_with_extension(module_specifier.as_url(), "js"); self.disk_cache.set(&js_key, contents.as_bytes())?; self.mark_compiled(module_specifier.as_url()); - let source_file = self - .file_fetcher - .fetch_cached_source_file(&module_specifier, Permissions::allow_all()) - .expect("Source file not found"); let version_hash = source_code_version_hash( &source_file.source_code, @@ -720,18 +798,6 @@ impl TsCompiler { module_specifier: &ModuleSpecifier, contents: &str, ) -> std::io::Result<()> { - let source_file = self - .file_fetcher - .fetch_cached_source_file(&module_specifier, Permissions::allow_all()) - .expect("Source file not found"); - - // NOTE: JavaScript files are only cached to disk if `checkJs` - // option in on - if source_file.media_type == msg::MediaType::JavaScript && !self.compile_js - { - return Ok(()); - } - let js_key = self .disk_cache .get_cache_filename_with_extension(module_specifier.as_url(), "js"); @@ -767,12 +833,12 @@ impl SourceMapGetter for TsCompiler { self .try_resolve_and_get_source_file(script_name) .and_then(|out| { - str::from_utf8(&out.source_code).ok().and_then(|v| { + str::from_utf8(&out.source_code).ok().map(|v| { // Do NOT use .lines(): it skips the terminating empty line. // (due to internally using .split_terminator() instead of .split()) let lines: Vec<&str> = v.split('\n').collect(); assert!(lines.len() > line); - Some(lines[line].to_string()) + lines[line].to_string() }) }) } @@ -854,14 +920,12 @@ pub async fn bundle( compiler_config: CompilerConfig, module_specifier: ModuleSpecifier, maybe_import_map: Option, - out_file: Option, unstable: bool, -) -> Result<(), ErrBox> { +) -> Result { debug!( "Invoking the compiler to bundle. module_name: {}", module_specifier.to_string() ); - eprintln!("Bundling {}", module_specifier.to_string()); let permissions = Permissions::allow_all(); let mut module_graph_loader = ModuleGraphLoader::new( @@ -871,7 +935,9 @@ pub async fn bundle( false, true, ); - module_graph_loader.add_to_graph(&module_specifier).await?; + module_graph_loader + .add_to_graph(&module_specifier, None) + .await?; let module_graph = module_graph_loader.get_graph(); let module_graph_json = serde_json::to_value(module_graph).expect("Failed to serialize data"); @@ -921,26 +987,7 @@ pub async fn bundle( assert!(bundle_response.bundle_output.is_some()); let output = bundle_response.bundle_output.unwrap(); - - // TODO(bartlomieju): the rest of this function should be handled - // in `main.rs` - it has nothing to do with TypeScript... - let output_string = fmt::format_text(&output)?; - - if let Some(out_file_) = out_file.as_ref() { - eprintln!("Emitting bundle to {:?}", out_file_); - - let output_bytes = output_string.as_bytes(); - let output_len = output_bytes.len(); - - deno_fs::write_file(out_file_, output_bytes, 0o666)?; - // TODO(bartlomieju): do we really need to show this info? (it doesn't respect --quiet flag) - // TODO(bartlomieju): add "humanFileSize" method - eprintln!("{} bytes emitted.", output_len); - } else { - println!("{}", output_string); - } - - Ok(()) + Ok(output) } /// This function is used by `Deno.compile()` and `Deno.bundle()` APIs. @@ -968,7 +1015,9 @@ pub async fn runtime_compile( let module_specifier = ModuleSpecifier::resolve_import(root_name, "")?; root_names.push(module_specifier.to_string()); - module_graph_loader.add_to_graph(&module_specifier).await?; + module_graph_loader + .add_to_graph(&module_specifier, None) + .await?; } // download all additional files from TSconfig and add them to root_names @@ -983,7 +1032,9 @@ pub async fn runtime_compile( .expect("type is not a string") .to_string(); let type_specifier = ModuleSpecifier::resolve_url_or_path(&type_str)?; - module_graph_loader.add_to_graph(&type_specifier).await?; + module_graph_loader + .add_to_graph(&type_specifier, None) + .await?; root_names.push(type_specifier.to_string()) } } @@ -1078,18 +1129,36 @@ mod tests { }; let mock_state = GlobalState::mock(vec![String::from("deno"), String::from("hello.ts")]); + + let mut module_graph_loader = ModuleGraphLoader::new( + mock_state.file_fetcher.clone(), + None, + Permissions::allow_all(), + false, + false, + ); + module_graph_loader + .add_to_graph(&specifier, None) + .await + .expect("Failed to create graph"); + let module_graph = module_graph_loader.get_graph(); + let result = mock_state .ts_compiler - .compile( + .compile_module_graph( mock_state.clone(), &out, TargetLib::Main, Permissions::allow_all(), - false, + module_graph, ) .await; assert!(result.is_ok()); - let source_code = result.unwrap().code; + let compiled_file = mock_state + .ts_compiler + .get_compiled_module(&out.url) + .unwrap(); + let source_code = compiled_file.code; assert!(source_code .as_bytes() .starts_with(b"\"use strict\";\nconsole.log(\"Hello World\");")); @@ -1143,7 +1212,6 @@ mod tests { CompilerConfig::load(None).unwrap(), module_name, None, - None, false, ) .await; diff --git a/cli/web_worker.rs b/cli/web_worker.rs index 46f03da363b226..e060a157d62f31 100644 --- a/cli/web_worker.rs +++ b/cli/web_worker.rs @@ -91,12 +91,7 @@ impl WebWorker { let mut worker = Worker::new(name, startup_data, state_); let terminated = Arc::new(AtomicBool::new(false)); - let isolate_handle = worker - .isolate - .v8_isolate - .as_mut() - .unwrap() - .thread_safe_handle(); + let isolate_handle = worker.isolate.thread_safe_handle(); let (terminate_tx, terminate_rx) = mpsc::channel::<()>(1); let handle = WebWorkerHandle { diff --git a/cli/worker.rs b/cli/worker.rs index 66aa98e6021c9e..70cadcc23b170f 100644 --- a/cli/worker.rs +++ b/cli/worker.rs @@ -1,9 +1,13 @@ // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. use crate::fmt_errors::JSError; +use crate::global_state::GlobalState; use crate::inspector::DenoInspector; use crate::ops; +use crate::ops::io::get_stdio; +use crate::startup_data; use crate::state::State; use deno_core::Buf; +use deno_core::CoreIsolate; use deno_core::ErrBox; use deno_core::ModuleId; use deno_core::ModuleSpecifier; @@ -86,7 +90,7 @@ fn create_channels() -> (WorkerChannelsInternal, WorkerHandle) { /// - `WebWorker` pub struct Worker { pub name: String, - pub isolate: Box, + pub isolate: deno_core::EsIsolate, pub inspector: Option>, pub state: State, pub waker: AtomicWaker, @@ -101,7 +105,9 @@ impl Worker { { let global_state = state.borrow().global_state.clone(); - isolate.set_js_error_create_fn(move |core_js_error| { + let core_state_rc = CoreIsolate::state(&isolate); + let mut core_state = core_state_rc.borrow_mut(); + core_state.set_js_error_create_fn(move |core_js_error| { JSError::create(core_js_error, &global_state.ts_compiler) }); } @@ -243,7 +249,8 @@ impl DerefMut for Worker { pub struct MainWorker(Worker); impl MainWorker { - pub fn new(name: String, startup_data: StartupData, state: State) -> Self { + // TODO(ry) combine MainWorker::new and MainWorker::create. + fn new(name: String, startup_data: StartupData, state: State) -> Self { let state_ = state.clone(); let mut worker = Worker::new(name, startup_data, state_); { @@ -271,6 +278,35 @@ impl MainWorker { } Self(worker) } + + pub fn create( + global_state: GlobalState, + main_module: ModuleSpecifier, + ) -> Result { + let state = State::new( + global_state.clone(), + None, + main_module, + global_state.maybe_import_map.clone(), + false, + )?; + let mut worker = MainWorker::new( + "main".to_string(), + startup_data::deno_isolate_init(), + state, + ); + { + let (stdin, stdout, stderr) = get_stdio(); + let state_rc = CoreIsolate::state(&worker.isolate); + let state = state_rc.borrow(); + let mut t = state.resource_table.borrow_mut(); + t.add("stdin", Box::new(stdin)); + t.add("stdout", Box::new(stdout)); + t.add("stderr", Box::new(stderr)); + } + worker.execute("bootstrap.mainRuntime()")?; + Ok(worker) + } } impl Deref for MainWorker { @@ -306,7 +342,8 @@ mod tests { ModuleSpecifier::resolve_url_or_path(&p.to_string_lossy()).unwrap(); let global_state = GlobalState::new(flags::Flags::default()).unwrap(); let state = - State::new(global_state, None, module_specifier.clone(), false).unwrap(); + State::new(global_state, None, module_specifier.clone(), None, false) + .unwrap(); let state_ = state.clone(); tokio_util::run_basic(async move { let mut worker = @@ -335,7 +372,8 @@ mod tests { ModuleSpecifier::resolve_url_or_path(&p.to_string_lossy()).unwrap(); let global_state = GlobalState::new(flags::Flags::default()).unwrap(); let state = - State::new(global_state, None, module_specifier.clone(), false).unwrap(); + State::new(global_state, None, module_specifier.clone(), None, false) + .unwrap(); let state_ = state.clone(); tokio_util::run_basic(async move { let mut worker = @@ -350,7 +388,6 @@ mod tests { }); let state = state_.borrow(); - assert_eq!(state.metrics.resolve_count, 1); // Check that we didn't start the compiler. assert_eq!(state.global_state.compiler_starts.load(Ordering::SeqCst), 0); } @@ -372,9 +409,14 @@ mod tests { ..flags::Flags::default() }; let global_state = GlobalState::new(flags).unwrap(); - let state = - State::new(global_state.clone(), None, module_specifier.clone(), false) - .unwrap(); + let state = State::new( + global_state.clone(), + None, + module_specifier.clone(), + None, + false, + ) + .unwrap(); let mut worker = MainWorker::new( "TEST".to_string(), startup_data::deno_isolate_init(), diff --git a/core/Cargo.toml b/core/Cargo.toml index ebd10838b3b95c..93da1dc90a4f62 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -2,7 +2,7 @@ [package] name = "deno_core" -version = "0.45.2" +version = "0.47.1" edition = "2018" description = "A secure JavaScript/TypeScript runtime built with V8, Rust, and Tokio" authors = ["the Deno authors"] @@ -15,12 +15,12 @@ path = "lib.rs" [dependencies] downcast-rs = "1.1.1" -futures = { version = "0.3.4", features = ["thread-pool", "compat"] } +futures = { version = "0.3.5", features = ["thread-pool", "compat"] } lazy_static = "1.4.0" -libc = "0.2.69" +libc = "0.2.71" log = "0.4.8" -rusty_v8 = "0.4.2" -serde_json = "1.0.52" +rusty_v8 = "0.5.0" +serde_json = "1.0.53" url = "2.1.1" [[example]] @@ -30,4 +30,4 @@ path = "examples/http_bench.rs" # These dependendencies are only used for deno_core_http_bench. [dev-dependencies] derive_deref = "1.1.0" -tokio = { version = "0.2.20", features = ["rt-core", "tcp"] } +tokio = { version = "0.2.21", features = ["rt-core", "tcp"] } diff --git a/core/any_error.rs b/core/any_error.rs deleted file mode 100644 index 34709e77fc38d1..00000000000000 --- a/core/any_error.rs +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. - -use std::any::Any; -use std::any::TypeId; -use std::convert::From; -use std::error::Error; -use std::fmt; -use std::ops::Deref; - -// The Send and Sync traits are required because deno is multithreaded and we -// need to be able to handle errors across threads. -pub trait AnyError: Any + Error + Send + Sync + 'static {} -impl AnyError for T where T: Any + Error + Send + Sync + Sized + 'static {} - -#[derive(Debug)] -pub struct ErrBox(Box); - -impl dyn AnyError { - pub fn downcast_ref(&self) -> Option<&T> { - if Any::type_id(self) == TypeId::of::() { - let target = self as *const Self as *const T; - let target = unsafe { &*target }; - Some(target) - } else { - None - } - } -} - -impl ErrBox { - pub fn downcast(self) -> Result { - if Any::type_id(&*self.0) == TypeId::of::() { - let target = Box::into_raw(self.0) as *mut T; - let target = unsafe { Box::from_raw(target) }; - Ok(*target) - } else { - Err(self) - } - } -} - -impl AsRef for ErrBox { - fn as_ref(&self) -> &dyn AnyError { - self.0.as_ref() - } -} - -impl Deref for ErrBox { - type Target = Box; - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl From for ErrBox { - fn from(error: T) -> Self { - Self(Box::new(error)) - } -} - -impl From> for ErrBox { - fn from(boxed: Box) -> Self { - Self(boxed) - } -} - -impl fmt::Display for ErrBox { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.0.fmt(f) - } -} diff --git a/core/bindings.rs b/core/bindings.rs index 16e78e368f0e10..9b66bfc481430f 100644 --- a/core/bindings.rs +++ b/core/bindings.rs @@ -1,9 +1,9 @@ // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. -use crate::es_isolate::EsIsolate; -use crate::isolate::CoreIsolate; -use crate::isolate::ZeroCopyBuf; -use crate::js_errors::JSError; +use crate::CoreIsolate; +use crate::EsIsolate; +use crate::JSError; +use crate::ZeroCopyBuf; use rusty_v8 as v8; use v8::MapFnTo; @@ -239,7 +239,8 @@ pub fn boxed_slice_to_uint8array<'sc>( let backing_store = v8::ArrayBuffer::new_backing_store_from_boxed_slice(buf); let backing_store_shared = backing_store.make_shared(); let ab = v8::ArrayBuffer::with_backing_store(scope, &backing_store_shared); - v8::Uint8Array::new(ab, 0, buf_len).expect("Failed to create UintArray8") + v8::Uint8Array::new(scope, ab, 0, buf_len) + .expect("Failed to create UintArray8") } pub extern "C" fn host_import_module_dynamically_callback( @@ -250,9 +251,6 @@ pub extern "C" fn host_import_module_dynamically_callback( let mut cbs = v8::CallbackScope::new_escapable(context); let mut hs = v8::EscapableHandleScope::new(cbs.enter()); let scope = hs.enter(); - let isolate = scope.isolate(); - let core_isolate: &mut EsIsolate = - unsafe { &mut *(isolate.get_data(1) as *mut EsIsolate) }; // NOTE(bartlomieju): will crash for non-UTF-8 specifier let specifier_str = specifier @@ -277,11 +275,11 @@ pub extern "C" fn host_import_module_dynamically_callback( let mut resolver_handle = v8::Global::new(); resolver_handle.set(scope, resolver); - core_isolate.dyn_import_cb( - resolver_handle, - &specifier_str, - &referrer_name_str, - ); + { + let state_rc = EsIsolate::state(scope.isolate()); + let mut state = state_rc.borrow_mut(); + state.dyn_import_cb(resolver_handle, &specifier_str, &referrer_name_str); + } &mut *scope.escape(promise) } @@ -294,14 +292,13 @@ pub extern "C" fn host_initialize_import_meta_object_callback( let mut cbs = v8::CallbackScope::new(context); let mut hs = v8::HandleScope::new(cbs.enter()); let scope = hs.enter(); - let isolate = scope.isolate(); - let core_isolate: &mut EsIsolate = - unsafe { &mut *(isolate.get_data(1) as *mut EsIsolate) }; + let state_rc = EsIsolate::state(scope.isolate()); + let state = state_rc.borrow(); let id = module.get_identity_hash(); assert_ne!(id, 0); - let info = core_isolate.modules.get_info(id).expect("Module not found"); + let info = state.modules.get_info(id).expect("Module not found"); meta.create_data_property( context, @@ -320,10 +317,10 @@ pub extern "C" fn promise_reject_callback(message: v8::PromiseRejectMessage) { let mut hs = v8::HandleScope::new(cbs.enter()); let scope = hs.enter(); - let core_isolate: &mut CoreIsolate = - unsafe { &mut *(scope.isolate().get_data(0) as *mut CoreIsolate) }; + let state_rc = CoreIsolate::state(scope.isolate()); + let mut state = state_rc.borrow_mut(); - let context = core_isolate.global_context.get(scope).unwrap(); + let context = state.global_context.get(scope).unwrap(); let mut cs = v8::ContextScope::new(scope, context); let scope = cs.enter(); @@ -335,13 +332,13 @@ pub extern "C" fn promise_reject_callback(message: v8::PromiseRejectMessage) { let error = message.get_value(); let mut error_global = v8::Global::::new(); error_global.set(scope, error); - core_isolate + state .pending_promise_exceptions .insert(promise_id, error_global); } v8::PromiseRejectEvent::PromiseHandlerAddedAfterReject => { if let Some(mut handle) = - core_isolate.pending_promise_exceptions.remove(&promise_id) + state.pending_promise_exceptions.remove(&promise_id) { handle.reset(scope); } @@ -415,17 +412,17 @@ fn recv( args: v8::FunctionCallbackArguments, _rv: v8::ReturnValue, ) { - let core_isolate: &mut CoreIsolate = - unsafe { &mut *(scope.isolate().get_data(0) as *mut CoreIsolate) }; + let state_rc = CoreIsolate::state(scope.isolate()); + let mut state = state_rc.borrow_mut(); - if !core_isolate.js_recv_cb.is_empty() { + if !state.js_recv_cb.is_empty() { let msg = v8::String::new(scope, "Deno.core.recv already called.").unwrap(); scope.isolate().throw_exception(msg.into()); return; } let recv_fn = v8::Local::::try_from(args.get(0)).unwrap(); - core_isolate.js_recv_cb.set(scope, recv_fn); + state.js_recv_cb.set(scope, recv_fn); } fn send( @@ -433,10 +430,6 @@ fn send( args: v8::FunctionCallbackArguments, mut rv: v8::ReturnValue, ) { - let core_isolate: &mut CoreIsolate = - unsafe { &mut *(scope.isolate().get_data(0) as *mut CoreIsolate) }; - assert!(!core_isolate.global_context.is_empty()); - let op_id = match v8::Local::::try_from(args.get(0)) { Ok(op_id) => op_id.value() as u32, Err(err) => { @@ -450,7 +443,7 @@ fn send( let control_backing_store: v8::SharedRef; let control = match v8::Local::::try_from(args.get(1)) { Ok(view) => unsafe { - control_backing_store = view.buffer().unwrap().get_backing_store(); + control_backing_store = view.buffer(scope).unwrap().get_backing_store(); get_backing_store_slice( &control_backing_store, view.byte_offset(), @@ -460,14 +453,50 @@ fn send( Err(_) => &[], }; - let zero_copy: Option = - v8::Local::::try_from(args.get(2)) - .map(ZeroCopyBuf::new) - .ok(); + let state_rc = CoreIsolate::state(scope.isolate()); + let mut state = state_rc.borrow_mut(); + assert!(!state.global_context.is_empty()); + + let mut buf_iter = (2..args.length()).map(|idx| { + v8::Local::::try_from(args.get(idx)) + .map(|view| ZeroCopyBuf::new(scope, view)) + .map_err(|err| { + let msg = format!("Invalid argument at position {}: {}", idx, err); + let msg = v8::String::new(scope, &msg).unwrap(); + v8::Exception::type_error(scope, msg) + }) + }); + + let mut buf_one: ZeroCopyBuf; + let mut buf_vec: Vec; + + // Collect all ArrayBufferView's + let buf_iter_result = match buf_iter.len() { + 0 => Ok(&mut [][..]), + 1 => match buf_iter.next().unwrap() { + Ok(buf) => { + buf_one = buf; + Ok(std::slice::from_mut(&mut buf_one)) + } + Err(err) => Err(err), + }, + _ => match buf_iter.collect::, _>>() { + Ok(v) => { + buf_vec = v; + Ok(&mut buf_vec[..]) + } + Err(err) => Err(err), + }, + }; // If response is empty then it's either async op or exception was thrown - let maybe_response = - core_isolate.dispatch_op(scope, op_id, control, zero_copy); + let maybe_response = match buf_iter_result { + Ok(bufs) => state.dispatch_op(scope, op_id, control, bufs), + Err(exc) => { + scope.isolate().throw_exception(exc); + return; + } + }; if let Some(response) = maybe_response { // Synchronous response. @@ -476,7 +505,7 @@ fn send( if !buf.is_empty() { let ui8 = boxed_slice_to_uint8array(scope, buf); - rv.set(ui8.into()) + rv.set(ui8.into()); } } } @@ -486,10 +515,10 @@ fn set_macrotask_callback( args: v8::FunctionCallbackArguments, _rv: v8::ReturnValue, ) { - let core_isolate: &mut CoreIsolate = - unsafe { &mut *(scope.isolate().get_data(0) as *mut CoreIsolate) }; + let state_rc = CoreIsolate::state(scope.isolate()); + let mut state = state_rc.borrow_mut(); - if !core_isolate.js_macrotask_cb.is_empty() { + if !state.js_macrotask_cb.is_empty() { let msg = v8::String::new(scope, "Deno.core.setMacrotaskCallback already called.") .unwrap(); @@ -499,7 +528,7 @@ fn set_macrotask_callback( let macrotask_cb_fn = v8::Local::::try_from(args.get(0)).unwrap(); - core_isolate.js_macrotask_cb.set(scope, macrotask_cb_fn); + state.js_macrotask_cb.set(scope, macrotask_cb_fn); } fn eval_context( @@ -507,10 +536,12 @@ fn eval_context( args: v8::FunctionCallbackArguments, mut rv: v8::ReturnValue, ) { - let core_isolate: &mut CoreIsolate = - unsafe { &mut *(scope.isolate().get_data(0) as *mut CoreIsolate) }; - assert!(!core_isolate.global_context.is_empty()); - let context = core_isolate.global_context.get(scope).unwrap(); + let state_rc = CoreIsolate::state(scope.isolate()); + let context = { + let state = state_rc.borrow(); + assert!(!state.global_context.is_empty()); + state.global_context.get(scope).unwrap() + }; let source = match v8::Local::::try_from(args.get(0)) { Ok(s) => s, @@ -545,7 +576,7 @@ fn eval_context( if maybe_script.is_none() { assert!(tc.has_caught()); - let exception = tc.exception().unwrap(); + let exception = tc.exception(scope).unwrap(); output.set( context, @@ -586,7 +617,7 @@ fn eval_context( if result.is_none() { assert!(tc.has_caught()); - let exception = tc.exception().unwrap(); + let exception = tc.exception(scope).unwrap(); output.set( context, @@ -643,10 +674,10 @@ fn format_error( args: v8::FunctionCallbackArguments, mut rv: v8::ReturnValue, ) { - let core_isolate: &mut CoreIsolate = - unsafe { &mut *(scope.isolate().get_data(0) as *mut CoreIsolate) }; let e = JSError::from_v8_exception(scope, args.get(0)); - let e = (core_isolate.js_error_create_fn)(e); + let state_rc = CoreIsolate::state(scope.isolate()); + let state = state_rc.borrow(); + let e = (state.js_error_create_fn)(e); let e = e.to_string(); let e = v8::String::new(scope, &e).unwrap(); rv.set(e.into()) @@ -671,14 +702,15 @@ fn encode( let buf = if text_bytes.is_empty() { let ab = v8::ArrayBuffer::new(scope, 0); - v8::Uint8Array::new(ab, 0, 0).expect("Failed to create UintArray8") + v8::Uint8Array::new(scope, ab, 0, 0).expect("Failed to create UintArray8") } else { let buf_len = text_bytes.len(); let backing_store = v8::ArrayBuffer::new_backing_store_from_boxed_slice(text_bytes); let backing_store_shared = backing_store.make_shared(); let ab = v8::ArrayBuffer::with_backing_store(scope, &backing_store_shared); - v8::Uint8Array::new(ab, 0, buf_len).expect("Failed to create UintArray8") + v8::Uint8Array::new(scope, ab, 0, buf_len) + .expect("Failed to create UintArray8") }; rv.set(buf.into()) @@ -699,7 +731,7 @@ fn decode( } }; - let backing_store = view.buffer().unwrap().get_backing_store(); + let backing_store = view.buffer(scope).unwrap().get_backing_store(); let buf = unsafe { get_backing_store_slice( &backing_store, @@ -734,19 +766,19 @@ fn shared_getter( _args: v8::PropertyCallbackArguments, mut rv: v8::ReturnValue, ) { - let core_isolate: &mut CoreIsolate = - unsafe { &mut *(scope.isolate().get_data(0) as *mut CoreIsolate) }; + let state_rc = CoreIsolate::state(scope.isolate()); + let mut state = state_rc.borrow_mut(); // Lazily initialize the persistent external ArrayBuffer. - if core_isolate.shared_ab.is_empty() { + if state.shared_ab.is_empty() { let ab = v8::SharedArrayBuffer::with_backing_store( scope, - core_isolate.shared.get_backing_store(), + state.shared.get_backing_store(), ); - core_isolate.shared_ab.set(scope, ab); + state.shared_ab.set(scope, ab); } - let shared_ab = core_isolate.shared_ab.get(scope).unwrap(); + let shared_ab = state.shared_ab.get(scope).unwrap(); rv.set(shared_ab.into()); } @@ -759,11 +791,11 @@ pub fn module_resolve_callback<'s>( let mut scope = v8::EscapableHandleScope::new(scope.enter()); let scope = scope.enter(); - let core_isolate: &mut EsIsolate = - unsafe { &mut *(scope.isolate().get_data(1) as *mut EsIsolate) }; + let state_rc = EsIsolate::state(scope.isolate()); + let mut state = state_rc.borrow_mut(); let referrer_id = referrer.get_identity_hash(); - let referrer_name = core_isolate + let referrer_name = state .modules .get_info(referrer_id) .expect("ModuleInfo not found") @@ -778,8 +810,8 @@ pub fn module_resolve_callback<'s>( let req_str = req.to_rust_string_lossy(scope); if req_str == specifier_str { - let id = core_isolate.module_resolve_cb(&req_str, referrer_id); - let maybe_info = core_isolate.modules.get_info(id); + let id = state.module_resolve_cb(&req_str, referrer_id); + let maybe_info = state.modules.get_info(id); if maybe_info.is_none() { let msg = format!( @@ -810,10 +842,10 @@ fn get_promise_details( args: v8::FunctionCallbackArguments, mut rv: v8::ReturnValue, ) { - let core_isolate: &mut CoreIsolate = - unsafe { &mut *(scope.isolate().get_data(0) as *mut CoreIsolate) }; - assert!(!core_isolate.global_context.is_empty()); - let context = core_isolate.global_context.get(scope).unwrap(); + let state_rc = CoreIsolate::state(scope.isolate()); + let state = state_rc.borrow(); + assert!(!state.global_context.is_empty()); + let context = state.global_context.get(scope).unwrap(); let promise = match v8::Local::::try_from(args.get(0)) { Ok(val) => val, diff --git a/core/core.js b/core/core.js index 4c6f708bb1932a..23cc325abfeabe 100644 --- a/core/core.js +++ b/core/core.js @@ -59,7 +59,7 @@ SharedQueue Binary Layout function ops() { // op id 0 is a special value to retrieve the map of registered ops. - const opsMapBytes = send(0, new Uint8Array([]), null); + const opsMapBytes = send(0, new Uint8Array([])); const opsMapJson = String.fromCharCode.apply(null, opsMapBytes); return JSON.parse(opsMapJson); } @@ -181,13 +181,9 @@ SharedQueue Binary Layout } } - function dispatch(opId, control, zeroCopy = null) { - return send(opId, control, zeroCopy); - } - Object.assign(window.Deno.core, { setAsyncHandler, - dispatch, + dispatch: send, ops, // sharedQueue is private but exposed for testing. sharedQueue: { diff --git a/core/isolate.rs b/core/core_isolate.rs similarity index 80% rename from core/isolate.rs rename to core/core_isolate.rs index 13892c2d9ab18a..984a6a2a6cf1c3 100644 --- a/core/isolate.rs +++ b/core/core_isolate.rs @@ -7,99 +7,34 @@ use rusty_v8 as v8; -use crate::any_error::ErrBox; use crate::bindings; -use crate::js_errors::JSError; use crate::ops::*; use crate::shared_queue::SharedQueue; use crate::shared_queue::RECOMMENDED_SIZE; +use crate::ErrBox; +use crate::JSError; use crate::ResourceTable; +use crate::ZeroCopyBuf; use futures::future::FutureExt; -use futures::stream::select; use futures::stream::FuturesUnordered; use futures::stream::StreamExt; use futures::task::AtomicWaker; use futures::Future; -use libc::c_void; use std::cell::RefCell; use std::collections::HashMap; use std::convert::From; -use std::error::Error; -use std::fmt; use std::mem::forget; -use std::ops::{Deref, DerefMut}; +use std::ops::Deref; +use std::ops::DerefMut; use std::option::Option; use std::pin::Pin; use std::rc::Rc; -use std::sync::{Arc, Mutex, Once}; +use std::sync::Once; use std::task::Context; use std::task::Poll; type PendingOpFuture = Pin>>; -/// A ZeroCopyBuf encapsulates a slice that's been borrowed from a JavaScript -/// ArrayBuffer object. JavaScript objects can normally be garbage collected, -/// but the existence of a ZeroCopyBuf inhibits this until it is dropped. It -/// behaves much like an Arc<[u8]>, although a ZeroCopyBuf currently can't be -/// cloned. -pub struct ZeroCopyBuf { - backing_store: v8::SharedRef, - byte_offset: usize, - byte_length: usize, -} - -unsafe impl Send for ZeroCopyBuf {} - -impl ZeroCopyBuf { - pub fn new(view: v8::Local) -> Self { - let backing_store = view.buffer().unwrap().get_backing_store(); - let byte_offset = view.byte_offset(); - let byte_length = view.byte_length(); - Self { - backing_store, - byte_offset, - byte_length, - } - } -} - -impl Deref for ZeroCopyBuf { - type Target = [u8]; - fn deref(&self) -> &[u8] { - unsafe { - bindings::get_backing_store_slice( - &self.backing_store, - self.byte_offset, - self.byte_length, - ) - } - } -} - -impl DerefMut for ZeroCopyBuf { - fn deref_mut(&mut self) -> &mut [u8] { - unsafe { - bindings::get_backing_store_slice_mut( - &self.backing_store, - self.byte_offset, - self.byte_length, - ) - } - } -} - -impl AsRef<[u8]> for ZeroCopyBuf { - fn as_ref(&self) -> &[u8] { - &*self - } -} - -impl AsMut<[u8]> for ZeroCopyBuf { - fn as_mut(&mut self) -> &mut [u8] { - &mut *self - } -} - /// Stores a script used to initialize a Isolate pub struct Script<'a> { pub source: &'a str, @@ -137,7 +72,6 @@ pub enum StartupData<'a> { } type JSErrorCreateFn = dyn Fn(JSError) -> ErrBox; -type IsolateErrorHandleFn = dyn FnMut(ErrBox) -> Result<(), ErrBox>; /// A single execution context of JavaScript. Corresponds roughly to the "Web /// Worker" concept in the DOM. An CoreIsolate is a Future that can be used with @@ -147,28 +81,53 @@ type IsolateErrorHandleFn = dyn FnMut(ErrBox) -> Result<(), ErrBox>; /// Ops are created in JavaScript by calling Deno.core.dispatch(), and in Rust /// by implementing dispatcher function that takes control buffer and optional zero copy buffer /// as arguments. An async Op corresponds exactly to a Promise in JavaScript. -#[allow(unused)] pub struct CoreIsolate { - pub v8_isolate: Option, + // This is an Option instead of just OwnedIsolate to workaround + // an safety issue with SnapshotCreator. See CoreIsolate::drop. + v8_isolate: Option, snapshot_creator: Option, has_snapshotted: bool, + needs_init: bool, + startup_script: Option, +} + +/// Internal state for CoreIsolate which is stored in one of v8::Isolate's +/// embedder slots. +pub struct CoreIsolateState { pub resource_table: Rc>, pub global_context: v8::Global, pub(crate) shared_ab: v8::Global, pub(crate) js_recv_cb: v8::Global, pub(crate) js_macrotask_cb: v8::Global, pub(crate) pending_promise_exceptions: HashMap>, - shared_isolate_handle: Arc>>, pub(crate) js_error_create_fn: Box, - needs_init: bool, pub(crate) shared: SharedQueue, pending_ops: FuturesUnordered, pending_unref_ops: FuturesUnordered, have_unpolled_ops: bool, - startup_script: Option, pub op_registry: OpRegistry, waker: AtomicWaker, - error_handler: Option>, +} + +// TODO(ry) The trait v8::InIsolate is superfluous. HandleScope::new should just +// take &mut v8::Isolate. +impl v8::InIsolate for CoreIsolate { + fn isolate(&mut self) -> &mut v8::Isolate { + self.v8_isolate.as_mut().unwrap() + } +} + +impl Deref for CoreIsolate { + type Target = v8::Isolate; + fn deref(&self) -> &v8::Isolate { + self.v8_isolate.as_ref().unwrap() + } +} + +impl DerefMut for CoreIsolate { + fn deref_mut(&mut self) -> &mut v8::Isolate { + self.v8_isolate.as_mut().unwrap() + } } impl Drop for CoreIsolate { @@ -193,8 +152,6 @@ impl Drop for CoreIsolate { } } -static DENO_INIT: Once = Once::new(); - #[allow(clippy::missing_safety_doc)] pub unsafe fn v8_init() { let platform = v8::new_default_platform().unwrap(); @@ -215,7 +172,8 @@ pub unsafe fn v8_init() { impl CoreIsolate { /// startup_data defines the snapshot or script used at startup to initialize /// the isolate. - pub fn new(startup_data: StartupData, will_snapshot: bool) -> Box { + pub fn new(startup_data: StartupData, will_snapshot: bool) -> Self { + static DENO_INIT: Once = Once::new(); DENO_INIT.call_once(|| { unsafe { v8_init() }; }); @@ -275,44 +233,29 @@ impl CoreIsolate { (isolate, None) }; - let shared = SharedQueue::new(RECOMMENDED_SIZE); - let needs_init = true; - - let core_isolate = Self { - v8_isolate: None, + isolate.set_slot(Rc::new(RefCell::new(CoreIsolateState { global_context, resource_table: Rc::new(RefCell::new(ResourceTable::default())), pending_promise_exceptions: HashMap::new(), shared_ab: v8::Global::::new(), js_recv_cb: v8::Global::::new(), js_macrotask_cb: v8::Global::::new(), - snapshot_creator: maybe_snapshot_creator, - has_snapshotted: false, - shared_isolate_handle: Arc::new(Mutex::new(None)), js_error_create_fn: Box::new(JSError::create), - shared, - needs_init, + shared: SharedQueue::new(RECOMMENDED_SIZE), pending_ops: FuturesUnordered::new(), pending_unref_ops: FuturesUnordered::new(), have_unpolled_ops: false, - startup_script, op_registry: OpRegistry::new(), waker: AtomicWaker::new(), - error_handler: None, - }; + }))); - let mut boxed_isolate = Box::new(core_isolate); - { - let core_isolate_ptr: *mut Self = Box::into_raw(boxed_isolate); - unsafe { isolate.set_data(0, core_isolate_ptr as *mut c_void) }; - boxed_isolate = unsafe { Box::from_raw(core_isolate_ptr) }; - let shared_handle_ptr = &mut *isolate; - *boxed_isolate.shared_isolate_handle.lock().unwrap() = - Some(shared_handle_ptr); - boxed_isolate.v8_isolate = Some(isolate); + Self { + v8_isolate: Some(isolate), + snapshot_creator: maybe_snapshot_creator, + has_snapshotted: false, + needs_init: true, + startup_script, } - - boxed_isolate } fn setup_isolate(mut isolate: v8::OwnedIsolate) -> v8::OwnedIsolate { @@ -321,30 +264,13 @@ impl CoreIsolate { isolate } - /// Defines the how Deno.core.dispatch() acts. - /// Called whenever Deno.core.dispatch() is called in JavaScript. zero_copy_buf - /// corresponds to the second argument of Deno.core.dispatch(). - /// - /// Requires runtime to explicitly ask for op ids before using any of the ops. - pub fn register_op(&mut self, name: &str, op: F) -> OpId - where - F: Fn(&mut CoreIsolate, &[u8], Option) -> Op + 'static, - { - self.op_registry.register(name, op) - } - - /// Allows a callback to be set whenever a V8 exception is made. This allows - /// the caller to wrap the JSError into an error. By default this callback - /// is set to JSError::create. - pub fn set_js_error_create_fn( - &mut self, - f: impl Fn(JSError) -> ErrBox + 'static, - ) { - self.js_error_create_fn = Box::new(f); + pub fn state(isolate: &v8::Isolate) -> Rc> { + let s = isolate.get_slot::>>().unwrap(); + s.clone() } /// Executes a bit of built-in JavaScript to provide Deno.sharedQueue. - pub(crate) fn shared_init(&mut self) { + fn shared_init(&mut self) { if self.needs_init { self.needs_init = false; js_check(self.execute("core.js", include_str!("core.js"))); @@ -355,46 +281,6 @@ impl CoreIsolate { } } - pub fn dispatch_op<'s>( - &mut self, - scope: &mut impl v8::ToLocal<'s>, - op_id: OpId, - control_buf: &[u8], - zero_copy_buf: Option, - ) -> Option<(OpId, Box<[u8]>)> { - let op = if let Some(dispatcher) = self.op_registry.get(op_id) { - dispatcher(self, control_buf, zero_copy_buf) - } else { - let message = - v8::String::new(scope, &format!("Unknown op id: {}", op_id)).unwrap(); - let exception = v8::Exception::type_error(scope, message); - scope.isolate().throw_exception(exception); - return None; - }; - - debug_assert_eq!(self.shared.size(), 0); - match op { - Op::Sync(buf) => { - // For sync messages, we always return the response via Deno.core.send's - // return value. Sync messages ignore the op_id. - let op_id = 0; - Some((op_id, buf)) - } - Op::Async(fut) => { - let fut2 = fut.map(move |buf| (op_id, buf)); - self.pending_ops.push(fut2.boxed_local()); - self.have_unpolled_ops = true; - None - } - Op::AsyncUnref(fut) => { - let fut2 = fut.map(move |buf| (op_id, buf)); - self.pending_unref_ops.push(fut2.boxed_local()); - self.have_unpolled_ops = true; - None - } - } - } - /// Executes traditional JavaScript code (traditional = not ES modules) /// /// ErrBox can be downcast to a type that exposes additional information about @@ -407,16 +293,17 @@ impl CoreIsolate { ) -> Result<(), ErrBox> { self.shared_init(); - let js_error_create_fn = &*self.js_error_create_fn; - let v8_isolate = self.v8_isolate.as_mut().unwrap(); + let state_rc = Self::state(self); + let state = state_rc.borrow(); - let mut hs = v8::HandleScope::new(v8_isolate); + let mut hs = v8::HandleScope::new(self.v8_isolate.as_mut().unwrap()); let scope = hs.enter(); - assert!(!self.global_context.is_empty()); - let context = self.global_context.get(scope).unwrap(); + let context = state.global_context.get(scope).unwrap(); let mut cs = v8::ContextScope::new(scope, context); let scope = cs.enter(); + drop(state); + let source = v8::String::new(scope, js_source).unwrap(); let name = v8::String::new(scope, js_filename).unwrap(); let origin = bindings::script_origin(scope, name); @@ -428,8 +315,8 @@ impl CoreIsolate { match v8::Script::compile(scope, context, source, Some(&origin)) { Some(script) => script, None => { - let exception = tc.exception().unwrap(); - return exception_to_err_result(scope, exception, js_error_create_fn); + let exception = tc.exception(scope).unwrap(); + return exception_to_err_result(scope, exception); } }; @@ -437,8 +324,8 @@ impl CoreIsolate { Some(_) => Ok(()), None => { assert!(tc.has_caught()); - let exception = tc.exception().unwrap(); - exception_to_err_result(scope, exception, js_error_create_fn) + let exception = tc.exception(scope).unwrap(); + exception_to_err_result(scope, exception) } } } @@ -451,6 +338,7 @@ impl CoreIsolate { /// different type if CoreIsolate::set_js_error_create_fn() has been used. pub fn snapshot(&mut self) -> v8::StartupData { assert!(self.snapshot_creator.is_some()); + let state = Self::state(self); // Note: create_blob() method must not be called from within a HandleScope. // The HandleScope created here is exited at the end of the block. @@ -459,7 +347,7 @@ impl CoreIsolate { let v8_isolate = self.v8_isolate.as_mut().unwrap(); let mut hs = v8::HandleScope::new(v8_isolate); let scope = hs.enter(); - self.global_context.reset(scope); + state.borrow_mut().global_context.reset(scope); } let snapshot_creator = self.snapshot_creator.as_mut().unwrap(); @@ -470,47 +358,59 @@ impl CoreIsolate { snapshot } + + /// Defines the how Deno.core.dispatch() acts. + /// Called whenever Deno.core.dispatch() is called in JavaScript. zero_copy_buf + /// corresponds to the second argument of Deno.core.dispatch(). + /// + /// Requires runtime to explicitly ask for op ids before using any of the ops. + pub fn register_op(&mut self, name: &str, op: F) -> OpId + where + F: Fn(&mut CoreIsolateState, &[u8], &mut [ZeroCopyBuf]) -> Op + 'static, + { + let state_rc = Self::state(self); + let mut state = state_rc.borrow_mut(); + state.op_registry.register(name, op) + } } impl Future for CoreIsolate { type Output = Result<(), ErrBox>; fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { - let inner = self.get_mut(); - inner.waker.register(cx.waker()); - inner.shared_init(); + let core_isolate = self.get_mut(); + core_isolate.shared_init(); - let v8_isolate = inner.v8_isolate.as_mut().unwrap(); - let js_error_create_fn = &*inner.js_error_create_fn; - let js_recv_cb = &inner.js_recv_cb; - let js_macrotask_cb = &inner.js_macrotask_cb; - let pending_promise_exceptions = &mut inner.pending_promise_exceptions; + let state_rc = Self::state(core_isolate); + { + let state = state_rc.borrow(); + state.waker.register(cx.waker()); + } - let mut hs = v8::HandleScope::new(v8_isolate); + let mut hs = v8::HandleScope::new(core_isolate); let scope = hs.enter(); - let context = inner.global_context.get(scope).unwrap(); + let context = { + let state = state_rc.borrow(); + state.global_context.get(scope).unwrap() + }; let mut cs = v8::ContextScope::new(scope, context); let scope = cs.enter(); - check_promise_exceptions( - scope, - pending_promise_exceptions, - js_error_create_fn, - )?; + check_promise_exceptions(scope)?; let mut overflow_response: Option<(OpId, Buf)> = None; loop { + let mut state = state_rc.borrow_mut(); // Now handle actual ops. - inner.have_unpolled_ops = false; - #[allow(clippy::match_wild_err_arm)] - match select(&mut inner.pending_ops, &mut inner.pending_unref_ops) - .poll_next_unpin(cx) - { + state.have_unpolled_ops = false; + + let pending_r = state.pending_ops.poll_next_unpin(cx); + match pending_r { Poll::Ready(None) => break, Poll::Pending => break, Poll::Ready(Some((op_id, buf))) => { - let successful_push = inner.shared.push(op_id, &buf); + let successful_push = state.shared.push(op_id, &buf); if !successful_push { // If we couldn't push the response to the shared queue, because // there wasn't enough size, we will return the buffer via the @@ -519,55 +419,142 @@ impl Future for CoreIsolate { break; } } - } + }; } - if inner.shared.size() > 0 { - async_op_response(scope, None, js_recv_cb, js_error_create_fn)?; - // The other side should have shifted off all the messages. - assert_eq!(inner.shared.size(), 0); + loop { + let mut state = state_rc.borrow_mut(); + let unref_r = state.pending_unref_ops.poll_next_unpin(cx); + #[allow(clippy::match_wild_err_arm)] + match unref_r { + Poll::Ready(None) => break, + Poll::Pending => break, + Poll::Ready(Some((op_id, buf))) => { + let successful_push = state.shared.push(op_id, &buf); + if !successful_push { + // If we couldn't push the response to the shared queue, because + // there wasn't enough size, we will return the buffer via the + // legacy route, using the argument of deno_respond. + overflow_response = Some((op_id, buf)); + break; + } + } + }; } - if let Some((op_id, buf)) = overflow_response.take() { - async_op_response( - scope, - Some((op_id, buf)), - js_recv_cb, - js_error_create_fn, - )?; + { + let state = state_rc.borrow(); + if state.shared.size() > 0 { + drop(state); + async_op_response(scope, None)?; + // The other side should have shifted off all the messages. + let state = state_rc.borrow(); + assert_eq!(state.shared.size(), 0); + } } - drain_macrotasks(scope, js_macrotask_cb, js_error_create_fn)?; + { + if let Some((op_id, buf)) = overflow_response.take() { + async_op_response(scope, Some((op_id, buf)))?; + } - check_promise_exceptions( - scope, - pending_promise_exceptions, - js_error_create_fn, - )?; + drain_macrotasks(scope)?; + + check_promise_exceptions(scope)?; + } + let state = state_rc.borrow(); // We're idle if pending_ops is empty. - if inner.pending_ops.is_empty() { + if state.pending_ops.is_empty() { Poll::Ready(Ok(())) } else { - if inner.have_unpolled_ops { - inner.waker.wake(); + if state.have_unpolled_ops { + state.waker.wake(); } Poll::Pending } } } +impl CoreIsolateState { + /// Defines the how Deno.core.dispatch() acts. + /// Called whenever Deno.core.dispatch() is called in JavaScript. zero_copy_buf + /// corresponds to the second argument of Deno.core.dispatch(). + /// + /// Requires runtime to explicitly ask for op ids before using any of the ops. + pub fn register_op(&mut self, name: &str, op: F) -> OpId + where + F: Fn(&mut CoreIsolateState, &[u8], &mut [ZeroCopyBuf]) -> Op + 'static, + { + self.op_registry.register(name, op) + } + + /// Allows a callback to be set whenever a V8 exception is made. This allows + /// the caller to wrap the JSError into an error. By default this callback + /// is set to JSError::create. + pub fn set_js_error_create_fn( + &mut self, + f: impl Fn(JSError) -> ErrBox + 'static, + ) { + self.js_error_create_fn = Box::new(f); + } + + pub fn dispatch_op<'s>( + &mut self, + scope: &mut impl v8::ToLocal<'s>, + op_id: OpId, + control_buf: &[u8], + zero_copy_bufs: &mut [ZeroCopyBuf], + ) -> Option<(OpId, Box<[u8]>)> { + let op = if let Some(dispatcher) = self.op_registry.get(op_id) { + dispatcher(self, control_buf, zero_copy_bufs) + } else { + let message = + v8::String::new(scope, &format!("Unknown op id: {}", op_id)).unwrap(); + let exception = v8::Exception::type_error(scope, message); + scope.isolate().throw_exception(exception); + return None; + }; + + debug_assert_eq!(self.shared.size(), 0); + match op { + Op::Sync(buf) => { + // For sync messages, we always return the response via Deno.core.send's + // return value. Sync messages ignore the op_id. + let op_id = 0; + Some((op_id, buf)) + } + Op::Async(fut) => { + let fut2 = fut.map(move |buf| (op_id, buf)); + self.pending_ops.push(fut2.boxed_local()); + self.have_unpolled_ops = true; + None + } + Op::AsyncUnref(fut) => { + let fut2 = fut.map(move |buf| (op_id, buf)); + self.pending_unref_ops.push(fut2.boxed_local()); + self.have_unpolled_ops = true; + None + } + } + } +} + fn async_op_response<'s>( scope: &mut impl v8::ToLocal<'s>, maybe_buf: Option<(OpId, Box<[u8]>)>, - js_recv_cb: &v8::Global, - js_error_create_fn: &JSErrorCreateFn, ) -> Result<(), ErrBox> { let context = scope.get_current_context().unwrap(); let global: v8::Local = context.global(scope).into(); - let js_recv_cb = js_recv_cb + + let state_rc = CoreIsolate::state(scope.isolate()); + let state = state_rc.borrow_mut(); + + let js_recv_cb = state + .js_recv_cb .get(scope) .expect("Deno.core.recv has not been called."); + drop(state); // TODO(piscisaureus): properly integrate TryCatch in the scope chain. let mut try_catch = v8::TryCatch::new(scope); @@ -584,22 +571,23 @@ fn async_op_response<'s>( None => js_recv_cb.call(scope, context, global, &[]), }; - match tc.exception() { + match tc.exception(scope) { None => Ok(()), - Some(exception) => { - exception_to_err_result(scope, exception, js_error_create_fn) - } + Some(exception) => exception_to_err_result(scope, exception), } } fn drain_macrotasks<'s>( scope: &mut impl v8::ToLocal<'s>, - js_macrotask_cb: &v8::Global, - js_error_create_fn: &JSErrorCreateFn, ) -> Result<(), ErrBox> { let context = scope.get_current_context().unwrap(); let global: v8::Local = context.global(scope).into(); - let js_macrotask_cb = js_macrotask_cb.get(scope); + + let js_macrotask_cb = { + let state_rc = CoreIsolate::state(scope.isolate()); + let state = state_rc.borrow_mut(); + state.js_macrotask_cb.get(scope) + }; if js_macrotask_cb.is_none() { return Ok(()); } @@ -614,8 +602,8 @@ fn drain_macrotasks<'s>( let is_done = js_macrotask_cb.call(scope, context, global, &[]); - if let Some(exception) = tc.exception() { - return exception_to_err_result(scope, exception, js_error_create_fn); + if let Some(exception) = tc.exception(scope) { + return exception_to_err_result(scope, exception); } let is_done = is_done.unwrap(); @@ -627,18 +615,9 @@ fn drain_macrotasks<'s>( Ok(()) } -pub(crate) fn attach_handle_to_error( - scope: &mut impl v8::InIsolate, - err: ErrBox, - handle: v8::Local, -) -> ErrBox { - ErrWithV8Handle::new(scope, err, handle).into() -} - pub(crate) fn exception_to_err_result<'s, T>( scope: &mut impl v8::ToLocal<'s>, exception: v8::Local, - js_error_create_fn: &JSErrorCreateFn, ) -> Result { // TODO(piscisaureus): in rusty_v8, `is_execution_terminating()` should // also be implemented on `struct Isolate`. @@ -666,7 +645,10 @@ pub(crate) fn exception_to_err_result<'s, T>( } let js_error = JSError::from_v8_exception(scope, exception); - let js_error = (js_error_create_fn)(js_error); + + let state_rc = CoreIsolate::state(scope.isolate()); + let state = state_rc.borrow(); + let js_error = (state.js_error_create_fn)(js_error); if is_terminating_exception { // Re-enable exception termination. @@ -680,13 +662,15 @@ pub(crate) fn exception_to_err_result<'s, T>( fn check_promise_exceptions<'s>( scope: &mut impl v8::ToLocal<'s>, - pending_promise_exceptions: &mut HashMap>, - js_error_create_fn: &JSErrorCreateFn, ) -> Result<(), ErrBox> { - if let Some(&key) = pending_promise_exceptions.keys().next() { - let handle = pending_promise_exceptions.remove(&key).unwrap(); + let state_rc = CoreIsolate::state(scope.isolate()); + let mut state = state_rc.borrow_mut(); + + if let Some(&key) = state.pending_promise_exceptions.keys().next() { + let handle = state.pending_promise_exceptions.remove(&key).unwrap(); + drop(state); let exception = handle.get(scope).expect("empty error handle"); - exception_to_err_result(scope, exception, js_error_create_fn) + exception_to_err_result(scope, exception) } else { Ok(()) } @@ -705,6 +689,7 @@ pub mod tests { use futures::future::lazy; use std::ops::FnOnce; use std::sync::atomic::{AtomicUsize, Ordering}; + use std::sync::Arc; pub fn run_in_task(f: F) where @@ -733,21 +718,22 @@ pub mod tests { pub enum Mode { Async, AsyncUnref, + AsyncZeroCopy(u8), OverflowReqSync, OverflowResSync, OverflowReqAsync, OverflowResAsync, } - pub fn setup(mode: Mode) -> (Box, Arc) { + pub fn setup(mode: Mode) -> (CoreIsolate, Arc) { let dispatch_count = Arc::new(AtomicUsize::new(0)); let dispatch_count_ = dispatch_count.clone(); let mut isolate = CoreIsolate::new(StartupData::None, false); - let dispatcher = move |_isolate: &mut CoreIsolate, + let dispatcher = move |_state: &mut CoreIsolateState, control: &[u8], - _zero_copy: Option| + zero_copy: &mut [ZeroCopyBuf]| -> Op { dispatch_count_.fetch_add(1, Ordering::Relaxed); match mode { @@ -767,6 +753,18 @@ pub mod tests { }; Op::AsyncUnref(fut.boxed()) } + Mode::AsyncZeroCopy(count) => { + assert_eq!(control.len(), 1); + assert_eq!(control[0], 24); + assert_eq!(zero_copy.len(), count as usize); + zero_copy.iter().enumerate().for_each(|(idx, buf)| { + assert_eq!(buf.len(), 1); + assert_eq!(idx, buf[0] as usize); + }); + + let buf = vec![43u8].into_boxed_slice(); + Op::Async(futures::future::ready(buf).boxed()) + } Mode::OverflowReqSync => { assert_eq!(control.len(), 100 * 1024 * 1024); let buf = vec![43u8].into_boxed_slice(); @@ -831,6 +829,48 @@ pub mod tests { assert_eq!(dispatch_count.load(Ordering::Relaxed), 2); } + #[test] + fn test_dispatch_no_zero_copy_buf() { + let (mut isolate, dispatch_count) = setup(Mode::AsyncZeroCopy(0)); + js_check(isolate.execute( + "filename.js", + r#" + let control = new Uint8Array([24]); + Deno.core.send(1, control); + "#, + )); + assert_eq!(dispatch_count.load(Ordering::Relaxed), 1); + } + + #[test] + fn test_dispatch_one_zero_copy_buf() { + let (mut isolate, dispatch_count) = setup(Mode::AsyncZeroCopy(1)); + js_check(isolate.execute( + "filename.js", + r#" + let control = new Uint8Array([24]); + let zero_copy = new Uint8Array([0]); + Deno.core.send(1, control, zero_copy); + "#, + )); + assert_eq!(dispatch_count.load(Ordering::Relaxed), 1); + } + + #[test] + fn test_dispatch_two_zero_copy_bufs() { + let (mut isolate, dispatch_count) = setup(Mode::AsyncZeroCopy(2)); + js_check(isolate.execute( + "filename.js", + r#" + let control = new Uint8Array([24]); + let zero_copy_a = new Uint8Array([0]); + let zero_copy_b = new Uint8Array([1]); + Deno.core.send(1, control, zero_copy_a, zero_copy_b); + "#, + )); + assert_eq!(dispatch_count.load(Ordering::Relaxed), 1); + } + #[test] fn test_poll_async_delayed_ops() { run_in_task(|cx| { @@ -1183,42 +1223,3 @@ pub mod tests { js_check(isolate2.execute("check.js", "if (a != 3) throw Error('x')")); } } - -// TODO(piscisaureus): rusty_v8 should implement the Error trait on -// values of type v8::Global. -pub struct ErrWithV8Handle { - err: ErrBox, - handle: v8::Global, -} - -impl ErrWithV8Handle { - pub fn new( - scope: &mut impl v8::InIsolate, - err: ErrBox, - handle: v8::Local, - ) -> Self { - let handle = v8::Global::new_from(scope, handle); - Self { err, handle } - } - - pub fn get_handle(&self) -> &v8::Global { - &self.handle - } -} - -unsafe impl Send for ErrWithV8Handle {} -unsafe impl Sync for ErrWithV8Handle {} - -impl Error for ErrWithV8Handle {} - -impl fmt::Display for ErrWithV8Handle { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.err.fmt(f) - } -} - -impl fmt::Debug for ErrWithV8Handle { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.err.fmt(f) - } -} diff --git a/core/js_errors.rs b/core/errors.rs similarity index 81% rename from core/js_errors.rs rename to core/errors.rs index e8ea5a342f529a..bc821b266fdc51 100644 --- a/core/js_errors.rs +++ b/core/errors.rs @@ -1,11 +1,76 @@ // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. -use crate::ErrBox; use rusty_v8 as v8; +use std::any::Any; +use std::any::TypeId; use std::convert::TryFrom; use std::convert::TryInto; use std::error::Error; use std::fmt; +use std::ops::Deref; + +// The Send and Sync traits are required because deno is multithreaded and we +// need to be able to handle errors across threads. +pub trait AnyError: Any + Error + Send + Sync + 'static {} +impl AnyError for T where T: Any + Error + Send + Sync + Sized + 'static {} + +#[derive(Debug)] +pub struct ErrBox(Box); + +impl dyn AnyError { + pub fn downcast_ref(&self) -> Option<&T> { + if Any::type_id(self) == TypeId::of::() { + let target = self as *const Self as *const T; + let target = unsafe { &*target }; + Some(target) + } else { + None + } + } +} + +impl ErrBox { + pub fn downcast(self) -> Result { + if Any::type_id(&*self.0) == TypeId::of::() { + let target = Box::into_raw(self.0) as *mut T; + let target = unsafe { Box::from_raw(target) }; + Ok(*target) + } else { + Err(self) + } + } +} + +impl AsRef for ErrBox { + fn as_ref(&self) -> &dyn AnyError { + self.0.as_ref() + } +} + +impl Deref for ErrBox { + type Target = Box; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl From for ErrBox { + fn from(error: T) -> Self { + Self(Box::new(error)) + } +} + +impl From> for ErrBox { + fn from(boxed: Box) -> Self { + Self(boxed) + } +} + +impl fmt::Display for ErrBox { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.0.fmt(f) + } +} /// A `JSError` represents an exception coming from V8, with stack frames and /// line numbers. The deno_cli crate defines another `JSError` type, which wraps @@ -298,3 +363,50 @@ impl fmt::Display for JSError { Ok(()) } } + +pub(crate) fn attach_handle_to_error( + scope: &mut impl v8::InIsolate, + err: ErrBox, + handle: v8::Local, +) -> ErrBox { + ErrWithV8Handle::new(scope, err, handle).into() +} + +// TODO(piscisaureus): rusty_v8 should implement the Error trait on +// values of type v8::Global. +pub struct ErrWithV8Handle { + err: ErrBox, + handle: v8::Global, +} + +impl ErrWithV8Handle { + pub fn new( + scope: &mut impl v8::InIsolate, + err: ErrBox, + handle: v8::Local, + ) -> Self { + let handle = v8::Global::new_from(scope, handle); + Self { err, handle } + } + + pub fn get_handle(&self) -> &v8::Global { + &self.handle + } +} + +unsafe impl Send for ErrWithV8Handle {} +unsafe impl Sync for ErrWithV8Handle {} + +impl Error for ErrWithV8Handle {} + +impl fmt::Display for ErrWithV8Handle { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.err.fmt(f) + } +} + +impl fmt::Debug for ErrWithV8Handle { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.err.fmt(f) + } +} diff --git a/core/es_isolate.rs b/core/es_isolate.rs index 73ff1c388dce7c..e14798407324e0 100644 --- a/core/es_isolate.rs +++ b/core/es_isolate.rs @@ -6,17 +6,17 @@ use rusty_v8 as v8; -use crate::any_error::ErrBox; use crate::bindings; +use crate::errors::ErrBox; +use crate::errors::ErrWithV8Handle; use crate::futures::FutureExt; -use crate::ErrWithV8Handle; use futures::ready; use futures::stream::FuturesUnordered; use futures::stream::StreamExt; use futures::stream::StreamFuture; use futures::task::AtomicWaker; use futures::Future; -use libc::c_void; +use std::cell::RefCell; use std::collections::HashMap; use std::convert::TryFrom; use std::ops::{Deref, DerefMut}; @@ -26,20 +26,19 @@ use std::rc::Rc; use std::task::Context; use std::task::Poll; -use crate::isolate::attach_handle_to_error; -use crate::isolate::exception_to_err_result; -use crate::isolate::CoreIsolate; -use crate::isolate::StartupData; +use crate::core_isolate::exception_to_err_result; +use crate::errors::attach_handle_to_error; use crate::module_specifier::ModuleSpecifier; use crate::modules::LoadState; +use crate::modules::ModuleId; +use crate::modules::ModuleLoadId; use crate::modules::ModuleLoader; use crate::modules::ModuleSource; use crate::modules::Modules; use crate::modules::PrepareLoadFuture; use crate::modules::RecursiveModuleLoad; - -pub type ModuleId = i32; -pub type ModuleLoadId = i32; +use crate::CoreIsolate; +use crate::StartupData; /// More specialized version of `CoreIsolate` that provides loading /// and execution of ES Modules. @@ -47,8 +46,9 @@ pub type ModuleLoadId = i32; /// Creating `EsIsolate` requires to pass `loader` argument /// that implements `ModuleLoader` trait - that way actual resolution and /// loading of modules can be customized by the implementor. -pub struct EsIsolate { - core_isolate: Box, +pub struct EsIsolate(CoreIsolate); + +pub struct EsIsolateState { loader: Rc, pub modules: Modules, pub(crate) dyn_import_map: @@ -63,13 +63,13 @@ impl Deref for EsIsolate { type Target = CoreIsolate; fn deref(&self) -> &Self::Target { - &self.core_isolate + &self.0 } } impl DerefMut for EsIsolate { fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.core_isolate + &mut self.0 } } @@ -78,38 +78,27 @@ impl EsIsolate { loader: Rc, startup_data: StartupData, will_snapshot: bool, - ) -> Box { + ) -> Self { let mut core_isolate = CoreIsolate::new(startup_data, will_snapshot); { - let v8_isolate = core_isolate.v8_isolate.as_mut().unwrap(); - v8_isolate.set_host_initialize_import_meta_object_callback( + core_isolate.set_host_initialize_import_meta_object_callback( bindings::host_initialize_import_meta_object_callback, ); - v8_isolate.set_host_import_module_dynamically_callback( + core_isolate.set_host_import_module_dynamically_callback( bindings::host_import_module_dynamically_callback, ); } - let es_isolate = Self { + core_isolate.set_slot(Rc::new(RefCell::new(EsIsolateState { modules: Modules::new(), loader, - core_isolate, dyn_import_map: HashMap::new(), preparing_dyn_imports: FuturesUnordered::new(), pending_dyn_imports: FuturesUnordered::new(), waker: AtomicWaker::new(), - }; + }))); - let mut boxed_es_isolate = Box::new(es_isolate); - { - let es_isolate_ptr: *mut Self = Box::into_raw(boxed_es_isolate); - boxed_es_isolate = unsafe { Box::from_raw(es_isolate_ptr) }; - unsafe { - let v8_isolate = boxed_es_isolate.v8_isolate.as_mut().unwrap(); - v8_isolate.set_data(1, es_isolate_ptr as *mut c_void); - }; - } - boxed_es_isolate + EsIsolate(core_isolate) } /// Low-level module creation. @@ -121,14 +110,13 @@ impl EsIsolate { name: &str, source: &str, ) -> Result { - let core_isolate = &mut self.core_isolate; - let v8_isolate = core_isolate.v8_isolate.as_mut().unwrap(); - let js_error_create_fn = &*core_isolate.js_error_create_fn; + let state_rc = Self::state(self); - let mut hs = v8::HandleScope::new(v8_isolate); + let core_state_rc = CoreIsolate::state(self); + let core_state = core_state_rc.borrow(); + let mut hs = v8::HandleScope::new(&mut self.0); let scope = hs.enter(); - assert!(!core_isolate.global_context.is_empty()); - let context = core_isolate.global_context.get(scope).unwrap(); + let context = core_state.global_context.get(scope).unwrap(); let mut cs = v8::ContextScope::new(scope, context); let scope = cs.enter(); @@ -145,11 +133,8 @@ impl EsIsolate { if tc.has_caught() { assert!(maybe_module.is_none()); - return exception_to_err_result( - scope, - tc.exception().unwrap(), - js_error_create_fn, - ); + let e = tc.exception(scope).unwrap(); + return exception_to_err_result(scope, e); } let module = maybe_module.unwrap(); @@ -159,16 +144,21 @@ impl EsIsolate { for i in 0..module.get_module_requests_length() { let import_specifier = module.get_module_request(i).to_rust_string_lossy(scope); + let state = state_rc.borrow(); let module_specifier = - self.loader.resolve(&import_specifier, name, false)?; + state.loader.resolve(&import_specifier, name, false)?; import_specifiers.push(module_specifier); } let mut handle = v8::Global::::new(); handle.set(scope, module); - self - .modules - .register(id, name, main, handle, import_specifiers); + + { + let mut state = state_rc.borrow_mut(); + state + .modules + .register(id, name, main, handle, import_specifiers); + } Ok(id) } @@ -178,32 +168,30 @@ impl EsIsolate { /// the V8 exception. By default this type is JSError, however it may be a /// different type if CoreIsolate::set_js_error_create_fn() has been used. fn mod_instantiate(&mut self, id: ModuleId) -> Result<(), ErrBox> { - let v8_isolate = self.core_isolate.v8_isolate.as_mut().unwrap(); - let js_error_create_fn = &*self.core_isolate.js_error_create_fn; + let state_rc = Self::state(self); + let state = state_rc.borrow(); - let mut hs = v8::HandleScope::new(v8_isolate); + let core_state_rc = CoreIsolate::state(self); + let core_state = core_state_rc.borrow(); + let mut hs = v8::HandleScope::new(&mut self.0); let scope = hs.enter(); - assert!(!self.core_isolate.global_context.is_empty()); - let context = self.core_isolate.global_context.get(scope).unwrap(); + let context = core_state.global_context.get(scope).unwrap(); let mut cs = v8::ContextScope::new(scope, context); let scope = cs.enter(); let mut try_catch = v8::TryCatch::new(scope); let tc = try_catch.enter(); - let module_info = match self.modules.get_info(id) { + let module_info = match state.modules.get_info(id) { Some(info) => info, None if id == 0 => return Ok(()), _ => panic!("module id {} not found in module table", id), }; let mut module = module_info.handle.get(scope).unwrap(); + drop(state); if module.get_status() == v8::ModuleStatus::Errored { - exception_to_err_result( - scope, - module.get_exception(), - js_error_create_fn, - )? + exception_to_err_result(scope, module.get_exception())? } let result = @@ -211,8 +199,8 @@ impl EsIsolate { match result { Some(_) => Ok(()), None => { - let exception = tc.exception().unwrap(); - exception_to_err_result(scope, exception, js_error_create_fn) + let exception = tc.exception(scope).unwrap(); + exception_to_err_result(scope, exception) } } } @@ -223,20 +211,24 @@ impl EsIsolate { /// the V8 exception. By default this type is JSError, however it may be a /// different type if CoreIsolate::set_js_error_create_fn() has been used. pub fn mod_evaluate(&mut self, id: ModuleId) -> Result<(), ErrBox> { - let core_isolate = &mut self.core_isolate; - let v8_isolate = core_isolate.v8_isolate.as_mut().unwrap(); - let js_error_create_fn = &*core_isolate.js_error_create_fn; + let state_rc = Self::state(self); + let state = state_rc.borrow(); + + let core_state_rc = CoreIsolate::state(self); - let mut hs = v8::HandleScope::new(v8_isolate); + let mut hs = v8::HandleScope::new(&mut self.0); let scope = hs.enter(); - assert!(!core_isolate.global_context.is_empty()); - let context = core_isolate.global_context.get(scope).unwrap(); + let context = { + let core_state = core_state_rc.borrow(); + core_state.global_context.get(scope).unwrap() + }; let mut cs = v8::ContextScope::new(scope, context); let scope = cs.enter(); - let info = self.modules.get_info(id).expect("ModuleInfo not found"); + let info = state.modules.get_info(id).expect("ModuleInfo not found"); let module = info.handle.get(scope).expect("Empty module handle"); let mut status = module.get_status(); + drop(state); if status == v8::ModuleStatus::Instantiated { // IMPORTANT: Top-level-await is enabled, which means that return value // of module evaluation is a promise. @@ -267,8 +259,9 @@ impl EsIsolate { let promise = v8::Local::::try_from(value) .expect("Expected to get promise as module evaluation result"); let promise_id = promise.get_identity_hash(); + let mut core_state = core_state_rc.borrow_mut(); if let Some(mut handle) = - core_isolate.pending_promise_exceptions.remove(&promise_id) + core_state.pending_promise_exceptions.remove(&promise_id) { handle.reset(scope); } @@ -281,68 +274,41 @@ impl EsIsolate { v8::ModuleStatus::Evaluated => Ok(()), v8::ModuleStatus::Errored => { let exception = module.get_exception(); - exception_to_err_result(scope, exception, js_error_create_fn) + exception_to_err_result(scope, exception) .map_err(|err| attach_handle_to_error(scope, err, exception)) } other => panic!("Unexpected module status {:?}", other), } } - // Called by V8 during `Isolate::mod_instantiate`. - pub fn module_resolve_cb( - &mut self, - specifier: &str, - referrer_id: ModuleId, - ) -> ModuleId { - let referrer = self.modules.get_name(referrer_id).unwrap(); - let specifier = self - .loader - .resolve(specifier, referrer, false) - .expect("Module should have been already resolved"); - self.modules.get_id(specifier.as_str()).unwrap_or(0) - } - - // Called by V8 during `Isolate::mod_instantiate`. - pub fn dyn_import_cb( - &mut self, - resolver_handle: v8::Global, - specifier: &str, - referrer: &str, - ) { - debug!("dyn_import specifier {} referrer {} ", specifier, referrer); - - let load = RecursiveModuleLoad::dynamic_import( - specifier, - referrer, - self.loader.clone(), - ); - self.dyn_import_map.insert(load.id, resolver_handle); - self.waker.wake(); - let fut = load.prepare().boxed_local(); - self.preparing_dyn_imports.push(fut); - } - fn dyn_import_error( &mut self, id: ModuleLoadId, err: ErrBox, ) -> Result<(), ErrBox> { - let core_isolate = &mut self.core_isolate; - let v8_isolate = core_isolate.v8_isolate.as_mut().unwrap(); + let state_rc = Self::state(self); + let mut state = state_rc.borrow_mut(); + + let core_state_rc = CoreIsolate::state(self); + let core_state = core_state_rc.borrow(); - let mut hs = v8::HandleScope::new(v8_isolate); + let mut hs = v8::HandleScope::new(&mut self.0); let scope = hs.enter(); - let context = core_isolate.global_context.get(scope).unwrap(); + let context = core_state.global_context.get(scope).unwrap(); let mut cs = v8::ContextScope::new(scope, context); let scope = cs.enter(); - let mut resolver_handle = self + drop(core_state); + + let mut resolver_handle = state .dyn_import_map .remove(&id) .expect("Invalid dyn import id"); let resolver = resolver_handle.get(scope).unwrap(); resolver_handle.reset(scope); + drop(state); + let exception = err .downcast_ref::() .and_then(|err| err.get_handle().get(scope)) @@ -362,29 +328,42 @@ impl EsIsolate { id: ModuleLoadId, mod_id: ModuleId, ) -> Result<(), ErrBox> { + let state_rc = Self::state(self); + + let core_state_rc = CoreIsolate::state(self); + debug!("dyn_import_done {} {:?}", id, mod_id); assert!(mod_id != 0); - let v8_isolate = self.core_isolate.v8_isolate.as_mut().unwrap(); - let mut hs = v8::HandleScope::new(v8_isolate); + let mut hs = v8::HandleScope::new(&mut self.0); let scope = hs.enter(); - assert!(!self.core_isolate.global_context.is_empty()); - let context = self.core_isolate.global_context.get(scope).unwrap(); + let context = { + let core_state = core_state_rc.borrow(); + core_state.global_context.get(scope).unwrap() + }; let mut cs = v8::ContextScope::new(scope, context); let scope = cs.enter(); - let mut resolver_handle = self - .dyn_import_map - .remove(&id) - .expect("Invalid dyn import id"); + let mut resolver_handle = { + let mut state = state_rc.borrow_mut(); + state + .dyn_import_map + .remove(&id) + .expect("Invalid dyn import id") + }; let resolver = resolver_handle.get(scope).unwrap(); resolver_handle.reset(scope); - let info = self - .modules - .get_info(mod_id) - .expect("Dyn import module info not found"); - // Resolution success - let mut module = info.handle.get(scope).unwrap(); + + let module = { + let state = state_rc.borrow(); + let info = state + .modules + .get_info(mod_id) + .expect("Dyn import module info not found"); + // Resolution success + info.handle.get(scope).unwrap() + }; assert_eq!(module.get_status(), v8::ModuleStatus::Evaluated); + let module_namespace = module.get_module_namespace(); resolver.resolve(context, module_namespace).unwrap(); scope.isolate().run_microtasks(); @@ -395,8 +374,14 @@ impl EsIsolate { &mut self, cx: &mut Context, ) -> Poll> { + let state_rc = Self::state(self); + loop { - match self.preparing_dyn_imports.poll_next_unpin(cx) { + let r = { + let mut state = state_rc.borrow_mut(); + state.preparing_dyn_imports.poll_next_unpin(cx) + }; + match r { Poll::Pending | Poll::Ready(None) => { // There are no active dynamic import loaders, or none are ready. return Poll::Ready(Ok(())); @@ -407,7 +392,8 @@ impl EsIsolate { match prepare_result { Ok(load) => { - self.pending_dyn_imports.push(load.into_future()); + let state = state_rc.borrow_mut(); + state.pending_dyn_imports.push(load.into_future()); } Err(err) => { self.dyn_import_error(dyn_import_id, err)?; @@ -419,8 +405,14 @@ impl EsIsolate { } fn poll_dyn_imports(&mut self, cx: &mut Context) -> Poll> { + let state_rc = Self::state(self); loop { - match self.pending_dyn_imports.poll_next_unpin(cx) { + let poll_result = { + let mut state = state_rc.borrow_mut(); + state.pending_dyn_imports.poll_next_unpin(cx) + }; + + match poll_result { Poll::Pending | Poll::Ready(None) => { // There are no active dynamic import loaders, or none are ready. return Poll::Ready(Ok(())); @@ -439,7 +431,8 @@ impl EsIsolate { match self.register_during_load(info, &mut load) { Ok(()) => { // Keep importing until it's fully drained - self.pending_dyn_imports.push(load.into_future()); + let state = state_rc.borrow_mut(); + state.pending_dyn_imports.push(load.into_future()); } Err(err) => self.dyn_import_error(dyn_import_id, err)?, } @@ -482,6 +475,7 @@ impl EsIsolate { let referrer_specifier = ModuleSpecifier::resolve_url(&module_url_found).unwrap(); + let state_rc = Self::state(self); // #A There are 3 cases to handle at this moment: // 1. Source code resolved result have the same module name as requested // and is not yet registered @@ -494,10 +488,18 @@ impl EsIsolate { // If necessary, register an alias. if module_url_specified != module_url_found { - self.modules.alias(&module_url_specified, &module_url_found); + let mut state = state_rc.borrow_mut(); + state + .modules + .alias(&module_url_specified, &module_url_found); } - let module_id = match self.modules.get_id(&module_url_found) { + let maybe_mod_id = { + let state = state_rc.borrow(); + state.modules.get_id(&module_url_found) + }; + + let module_id = match maybe_mod_id { Some(id) => { // Module has already been registered. debug!( @@ -511,10 +513,19 @@ impl EsIsolate { }; // Now we must iterate over all imports of the module and load them. - let imports = self.modules.get_children(module_id).unwrap(); + let imports = { + let state_rc = Self::state(self); + let state = state_rc.borrow(); + state.modules.get_children(module_id).unwrap().clone() + }; for module_specifier in imports { - if !self.modules.is_registered(module_specifier) { + let is_registered = { + let state_rc = Self::state(self); + let state = state_rc.borrow(); + state.modules.is_registered(&module_specifier) + }; + if !is_registered { load .add_import(module_specifier.to_owned(), referrer_specifier.clone()); } @@ -542,11 +553,13 @@ impl EsIsolate { specifier: &ModuleSpecifier, code: Option, ) -> Result { - let load = RecursiveModuleLoad::main( - &specifier.to_string(), - code, - self.loader.clone(), - ); + let loader = { + let state_rc = Self::state(self); + let state = state_rc.borrow(); + state.loader.clone() + }; + + let load = RecursiveModuleLoad::main(&specifier.to_string(), code, loader); let (_load_id, prepare_result) = load.prepare().await; let mut load = prepare_result?; @@ -559,30 +572,49 @@ impl EsIsolate { let root_id = load.root_module_id.expect("Root module id empty"); self.mod_instantiate(root_id).map(|_| root_id) } + + pub fn state(isolate: &v8::Isolate) -> Rc> { + let s = isolate.get_slot::>>().unwrap(); + s.clone() + } } impl Future for EsIsolate { type Output = Result<(), ErrBox>; fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { - let inner = self.get_mut(); + let es_isolate = self.get_mut(); - inner.waker.register(cx.waker()); + let state_rc = Self::state(es_isolate); - if !inner.preparing_dyn_imports.is_empty() { - let poll_imports = inner.prepare_dyn_imports(cx)?; + { + let state = state_rc.borrow(); + state.waker.register(cx.waker()); + } + + let has_preparing = { + let state = state_rc.borrow(); + !state.preparing_dyn_imports.is_empty() + }; + if has_preparing { + let poll_imports = es_isolate.prepare_dyn_imports(cx)?; assert!(poll_imports.is_ready()); } - if !inner.pending_dyn_imports.is_empty() { - let poll_imports = inner.poll_dyn_imports(cx)?; + let has_pending = { + let state = state_rc.borrow(); + !state.pending_dyn_imports.is_empty() + }; + if has_pending { + let poll_imports = es_isolate.poll_dyn_imports(cx)?; assert!(poll_imports.is_ready()); } - match ready!(inner.core_isolate.poll_unpin(cx)) { + match ready!(es_isolate.0.poll_unpin(cx)) { Ok(()) => { - if inner.pending_dyn_imports.is_empty() - && inner.preparing_dyn_imports.is_empty() + let state = state_rc.borrow(); + if state.pending_dyn_imports.is_empty() + && state.preparing_dyn_imports.is_empty() { Poll::Ready(Ok(())) } else { @@ -594,14 +626,51 @@ impl Future for EsIsolate { } } +impl EsIsolateState { + // Called by V8 during `Isolate::mod_instantiate`. + pub fn module_resolve_cb( + &mut self, + specifier: &str, + referrer_id: ModuleId, + ) -> ModuleId { + let referrer = self.modules.get_name(referrer_id).unwrap(); + let specifier = self + .loader + .resolve(specifier, referrer, false) + .expect("Module should have been already resolved"); + self.modules.get_id(specifier.as_str()).unwrap_or(0) + } + + // Called by V8 during `Isolate::mod_instantiate`. + pub fn dyn_import_cb( + &mut self, + resolver_handle: v8::Global, + specifier: &str, + referrer: &str, + ) { + debug!("dyn_import specifier {} referrer {} ", specifier, referrer); + + let load = RecursiveModuleLoad::dynamic_import( + specifier, + referrer, + self.loader.clone(), + ); + self.dyn_import_map.insert(load.id, resolver_handle); + self.waker.wake(); + let fut = load.prepare().boxed_local(); + self.preparing_dyn_imports.push(fut); + } +} + #[cfg(test)] pub mod tests { use super::*; - use crate::isolate::js_check; - use crate::isolate::tests::run_in_task; - use crate::isolate::ZeroCopyBuf; + use crate::core_isolate::tests::run_in_task; + use crate::core_isolate::CoreIsolateState; + use crate::js_check; use crate::modules::ModuleSourceFuture; use crate::ops::*; + use crate::ZeroCopyBuf; use std::io; use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::Arc; @@ -644,9 +713,9 @@ pub mod tests { let mut isolate = EsIsolate::new(loader, StartupData::None, false); - let dispatcher = move |_isolate: &mut CoreIsolate, + let dispatcher = move |_state: &mut CoreIsolateState, control: &[u8], - _zero_copy: Option| + _zero_copy: &mut [ZeroCopyBuf]| -> Op { dispatch_count_.fetch_add(1, Ordering::Relaxed); assert_eq!(control.len(), 1); @@ -685,16 +754,23 @@ pub mod tests { .unwrap(); assert_eq!(dispatch_count.load(Ordering::Relaxed), 0); - let imports = isolate.modules.get_children(mod_a); - assert_eq!( - imports, - Some(&vec![ModuleSpecifier::resolve_url("file:///b.js").unwrap()]) - ); + let state_rc = EsIsolate::state(&isolate); + { + let state = state_rc.borrow(); + let imports = state.modules.get_children(mod_a); + assert_eq!( + imports, + Some(&vec![ModuleSpecifier::resolve_url("file:///b.js").unwrap()]) + ); + } let mod_b = isolate .mod_new(false, "file:///b.js", "export function b() { return 'b' }") .unwrap(); - let imports = isolate.modules.get_children(mod_b).unwrap(); - assert_eq!(imports.len(), 0); + { + let state = state_rc.borrow(); + let imports = state.modules.get_children(mod_b).unwrap(); + assert_eq!(imports.len(), 0); + } js_check(isolate.mod_instantiate(mod_b)); assert_eq!(dispatch_count.load(Ordering::Relaxed), 0); @@ -764,145 +840,57 @@ pub mod tests { }) } - /* - // Note from Bert: I do not understand how this part is supposed to pass. - // For me all these modules load in parallel and, unless I'm missing - // something, that's how it should be. So I disabled the test for now. - #[test] - fn dyn_import_err2() { - #[derive(Clone, Default)] - struct DynImportErr2Loader { - pub count: Arc, - } - - impl ModuleLoader for DynImportErr2Loader { - fn resolve( - &self, - specifier: &str, - referrer: &str, - _is_main: bool, - _is_dyn_import: bool, - ) -> Result { - let c = self.count.fetch_add(1, Ordering::Relaxed); - match c { - 0 => assert_eq!(specifier, "/foo1.js"), - 1 => assert_eq!(specifier, "/foo2.js"), - 2 => assert_eq!(specifier, "/foo3.js"), - _ => unreachable!(), - } - assert_eq!(referrer, "file:///dyn_import_error.js"); - let s = ModuleSpecifier::resolve_import(specifier, referrer).unwrap(); - Ok(s) - } + #[derive(Clone, Default)] + struct DynImportOkLoader { + pub prepare_load_count: Arc, + pub resolve_count: Arc, + pub load_count: Arc, + } - fn load( - &self, - specifier: &ModuleSpecifier, - _maybe_referrer: Option, - ) -> Pin> { - let info = ModuleSource { - module_url_specified: specifier.to_string(), - module_url_found: specifier.to_string(), - code: "# not valid JS".to_owned(), - }; - async move { Ok(info) }.boxed() - } + impl ModuleLoader for DynImportOkLoader { + fn resolve( + &self, + specifier: &str, + referrer: &str, + _is_main: bool, + ) -> Result { + let c = self.resolve_count.fetch_add(1, Ordering::Relaxed); + assert!(c < 4); + assert_eq!(specifier, "./b.js"); + assert_eq!(referrer, "file:///dyn_import3.js"); + let s = ModuleSpecifier::resolve_import(specifier, referrer).unwrap(); + Ok(s) } - // Import multiple modules to demonstrate that after failed dynamic import - // another dynamic import can still be run - run_in_task(|cx| { - let loader = Box::new(DynImportErr2Loader::default()); - let loader1 = loader.clone(); - let mut isolate = EsIsolate::new(loader, StartupData::None, false); - - js_check(isolate.execute( - "file:///dyn_import_error.js", - r#" - (async () => { - await import("/foo1.js"); - })(); - (async () => { - await import("/foo2.js"); - })(); - (async () => { - await import("/foo3.js"); - })(); - "#, - )); + fn load( + &self, + specifier: &ModuleSpecifier, + _maybe_referrer: Option, + _is_dyn_import: bool, + ) -> Pin> { + self.load_count.fetch_add(1, Ordering::Relaxed); + let info = ModuleSource { + module_url_specified: specifier.to_string(), + module_url_found: specifier.to_string(), + code: "export function b() { return 'b' }".to_owned(), + }; + async move { Ok(info) }.boxed() + } - assert_eq!(loader1.count.load(Ordering::Relaxed), 0); - // Now each poll should return error - assert!(match isolate.poll_unpin(cx) { - Poll::Ready(Err(_)) => true, - _ => false, - }); - assert_eq!(loader1.count.load(Ordering::Relaxed), 1); - assert!(match isolate.poll_unpin(cx) { - Poll::Ready(Err(_)) => true, - _ => false, - }); - assert_eq!(loader1.count.load(Ordering::Relaxed), 2); - assert!(match isolate.poll_unpin(cx) { - Poll::Ready(Err(_)) => true, - _ => false, - }); - assert_eq!(loader1.count.load(Ordering::Relaxed), 3); - }) + fn prepare_load( + &self, + _load_id: ModuleLoadId, + _module_specifier: &ModuleSpecifier, + _maybe_referrer: Option, + _is_dyn_import: bool, + ) -> Pin>>> { + self.prepare_load_count.fetch_add(1, Ordering::Relaxed); + async { Ok(()) }.boxed_local() + } } - */ #[test] fn dyn_import_ok() { - #[derive(Clone, Default)] - struct DynImportOkLoader { - pub prepare_load_count: Arc, - pub resolve_count: Arc, - pub load_count: Arc, - } - - impl ModuleLoader for DynImportOkLoader { - fn resolve( - &self, - specifier: &str, - referrer: &str, - _is_main: bool, - ) -> Result { - let c = self.resolve_count.fetch_add(1, Ordering::Relaxed); - assert!(c < 4); - assert_eq!(specifier, "./b.js"); - assert_eq!(referrer, "file:///dyn_import3.js"); - let s = ModuleSpecifier::resolve_import(specifier, referrer).unwrap(); - Ok(s) - } - - fn load( - &self, - specifier: &ModuleSpecifier, - _maybe_referrer: Option, - _is_dyn_import: bool, - ) -> Pin> { - self.load_count.fetch_add(1, Ordering::Relaxed); - let info = ModuleSource { - module_url_specified: specifier.to_string(), - module_url_found: specifier.to_string(), - code: "export function b() { return 'b' }".to_owned(), - }; - async move { Ok(info) }.boxed() - } - - fn prepare_load( - &self, - _load_id: ModuleLoadId, - _module_specifier: &ModuleSpecifier, - _maybe_referrer: Option, - _is_dyn_import: bool, - ) -> Pin>>> { - self.prepare_load_count.fetch_add(1, Ordering::Relaxed); - async { Ok(()) }.boxed_local() - } - } - run_in_task(|cx| { let loader = Rc::new(DynImportOkLoader::default()); let prepare_load_count = loader.prepare_load_count.clone(); @@ -950,4 +938,32 @@ pub mod tests { assert_eq!(load_count.load(Ordering::Relaxed), 2); }) } + + #[test] + fn dyn_import_borrow_mut_error() { + // https://github.com/denoland/deno/issues/6054 + run_in_task(|cx| { + let loader = Rc::new(DynImportOkLoader::default()); + let prepare_load_count = loader.prepare_load_count.clone(); + let mut isolate = EsIsolate::new(loader, StartupData::None, false); + js_check(isolate.execute( + "file:///dyn_import3.js", + r#" + (async () => { + let mod = await import("./b.js"); + if (mod.b() !== 'b') { + throw Error("bad"); + } + // Now do any op + Deno.core.ops(); + })(); + "#, + )); + // First poll runs `prepare_load` hook. + let _ = isolate.poll_unpin(cx); + assert_eq!(prepare_load_count.load(Ordering::Relaxed), 1); + // Second poll triggers error + let _ = isolate.poll_unpin(cx); + }) + } } diff --git a/core/examples/http_bench.js b/core/examples/http_bench.js index d9878cbe741d35..a893dab40816df 100644 --- a/core/examples/http_bench.js +++ b/core/examples/http_bench.js @@ -36,18 +36,18 @@ const scratchBytes = new Uint8Array( ); assert(scratchBytes.byteLength === 3 * 4); -function send(promiseId, opId, rid, zeroCopy = null) { +function send(promiseId, opId, rid, ...zeroCopy) { scratch32[0] = promiseId; scratch32[1] = rid; scratch32[2] = -1; - return Deno.core.dispatch(opId, scratchBytes, zeroCopy); + return Deno.core.dispatch(opId, scratchBytes, ...zeroCopy); } /** Returns Promise */ -function sendAsync(opId, rid, zeroCopy = null) { +function sendAsync(opId, rid, ...zeroCopy) { const promiseId = nextPromiseId++; const p = createResolvable(); - const buf = send(promiseId, opId, rid, zeroCopy); + const buf = send(promiseId, opId, rid, ...zeroCopy); if (buf) { const record = recordFromBuf(buf); // Sync result. diff --git a/core/examples/http_bench.rs b/core/examples/http_bench.rs index f728b2a69d3c66..233864fac1e6e7 100644 --- a/core/examples/http_bench.rs +++ b/core/examples/http_bench.rs @@ -4,6 +4,7 @@ extern crate derive_deref; extern crate log; use deno_core::CoreIsolate; +use deno_core::CoreIsolateState; use deno_core::Op; use deno_core::ResourceTable; use deno_core::Script; @@ -77,7 +78,7 @@ impl From for RecordBuf { } struct Isolate { - core_isolate: Box, // Unclear why CoreIsolate::new() returns a box. + core_isolate: CoreIsolate, state: State, } @@ -112,19 +113,19 @@ impl Isolate { fn register_sync_op(&mut self, name: &'static str, handler: F) where - F: 'static + Fn(State, u32, Option) -> Result, + F: 'static + Fn(State, u32, &mut [ZeroCopyBuf]) -> Result, { let state = self.state.clone(); - let core_handler = move |_isolate: &mut CoreIsolate, + let core_handler = move |_isolate_state: &mut CoreIsolateState, control_buf: &[u8], - zero_copy_buf: Option| + zero_copy_bufs: &mut [ZeroCopyBuf]| -> Op { let state = state.clone(); let record = Record::from(control_buf); let is_sync = record.promise_id == 0; assert!(is_sync); - let result: i32 = match handler(state, record.rid, zero_copy_buf) { + let result: i32 = match handler(state, record.rid, zero_copy_bufs) { Ok(r) => r as i32, Err(_) => -1, }; @@ -138,24 +139,25 @@ impl Isolate { fn register_op( &mut self, name: &'static str, - handler: impl Fn(State, u32, Option) -> F + Copy + 'static, + handler: impl Fn(State, u32, &mut [ZeroCopyBuf]) -> F + Copy + 'static, ) where F: TryFuture, F::Ok: TryInto, >::Error: Debug, { let state = self.state.clone(); - let core_handler = move |_isolate: &mut CoreIsolate, + let core_handler = move |_isolate_state: &mut CoreIsolateState, control_buf: &[u8], - zero_copy_buf: Option| + zero_copy_bufs: &mut [ZeroCopyBuf]| -> Op { let state = state.clone(); let record = Record::from(control_buf); let is_sync = record.promise_id == 0; assert!(!is_sync); + let mut zero_copy = zero_copy_bufs.to_vec(); let fut = async move { - let op = handler(state, record.rid, zero_copy_buf); + let op = handler(state, record.rid, &mut zero_copy); let result = op .map_ok(|r| r.try_into().expect("op result does not fit in i32")) .unwrap_or_else(|_| -1) @@ -181,7 +183,7 @@ impl Future for Isolate { fn op_close( state: State, rid: u32, - _buf: Option, + _buf: &mut [ZeroCopyBuf], ) -> Result { debug!("close rid={}", rid); let resource_table = &mut state.borrow_mut().resource_table; @@ -194,7 +196,7 @@ fn op_close( fn op_listen( state: State, _rid: u32, - _buf: Option, + _buf: &mut [ZeroCopyBuf], ) -> Result { debug!("listen"); let addr = "127.0.0.1:4544".parse::().unwrap(); @@ -208,7 +210,7 @@ fn op_listen( fn op_accept( state: State, rid: u32, - _buf: Option, + _buf: &mut [ZeroCopyBuf], ) -> impl TryFuture { debug!("accept rid={}", rid); @@ -226,9 +228,11 @@ fn op_accept( fn op_read( state: State, rid: u32, - buf: Option, + bufs: &mut [ZeroCopyBuf], ) -> impl TryFuture { - let mut buf = buf.unwrap(); + assert_eq!(bufs.len(), 1, "Invalid number of arguments"); + let mut buf = bufs[0].clone(); + debug!("read rid={}", rid); poll_fn(move |cx| { @@ -243,9 +247,10 @@ fn op_read( fn op_write( state: State, rid: u32, - buf: Option, + bufs: &mut [ZeroCopyBuf], ) -> impl TryFuture { - let buf = buf.unwrap(); + assert_eq!(bufs.len(), 1, "Invalid number of arguments"); + let buf = bufs[0].clone(); debug!("write rid={}", rid); poll_fn(move |cx| { diff --git a/core/lib.rs b/core/lib.rs index ffccc8febb5bc4..47ded645e71f76 100644 --- a/core/lib.rs +++ b/core/lib.rs @@ -8,30 +8,46 @@ extern crate lazy_static; #[macro_use] extern crate log; -mod any_error; mod bindings; +mod core_isolate; +mod errors; mod es_isolate; mod flags; -mod isolate; -mod js_errors; mod module_specifier; mod modules; mod ops; pub mod plugin_api; mod resources; mod shared_queue; +mod zero_copy_buf; pub use rusty_v8 as v8; -pub use crate::any_error::*; -pub use crate::es_isolate::*; +pub use crate::core_isolate::js_check; +pub use crate::core_isolate::CoreIsolate; +pub use crate::core_isolate::CoreIsolateState; +pub use crate::core_isolate::Script; +pub use crate::core_isolate::Snapshot; +pub use crate::core_isolate::StartupData; +pub use crate::errors::ErrBox; +pub use crate::errors::JSError; +pub use crate::es_isolate::EsIsolate; +pub use crate::es_isolate::EsIsolateState; pub use crate::flags::v8_set_flags; -pub use crate::isolate::*; -pub use crate::js_errors::*; -pub use crate::module_specifier::*; -pub use crate::modules::*; -pub use crate::ops::*; -pub use crate::resources::*; +pub use crate::module_specifier::ModuleResolutionError; +pub use crate::module_specifier::ModuleSpecifier; +pub use crate::modules::ModuleId; +pub use crate::modules::ModuleLoadId; +pub use crate::modules::ModuleLoader; +pub use crate::modules::ModuleSource; +pub use crate::modules::ModuleSourceFuture; +pub use crate::modules::RecursiveModuleLoad; +pub use crate::ops::Buf; +pub use crate::ops::Op; +pub use crate::ops::OpAsyncFuture; +pub use crate::ops::OpId; +pub use crate::resources::ResourceTable; +pub use crate::zero_copy_buf::ZeroCopyBuf; pub fn v8_version() -> &'static str { v8::V8::get_version() diff --git a/core/modules.rs b/core/modules.rs index 632df2dd000b2f..ca850d0bb04f65 100644 --- a/core/modules.rs +++ b/core/modules.rs @@ -2,10 +2,8 @@ use rusty_v8 as v8; -use crate::any_error::ErrBox; -use crate::es_isolate::ModuleId; -use crate::es_isolate::ModuleLoadId; use crate::module_specifier::ModuleSpecifier; +use crate::ErrBox; use futures::future::FutureExt; use futures::stream::FuturesUnordered; use futures::stream::Stream; @@ -25,6 +23,9 @@ lazy_static! { pub static ref NEXT_LOAD_ID: AtomicI32 = AtomicI32::new(0); } +pub type ModuleId = i32; +pub type ModuleLoadId = i32; + /// EsModule source code that will be loaded into V8. /// /// Users can implement `Into` for different file types that @@ -548,7 +549,8 @@ macro_rules! include_crate_modules { mod tests { use super::*; use crate::es_isolate::EsIsolate; - use crate::isolate::js_check; + use crate::js_check; + use crate::StartupData; use futures::future::FutureExt; use std::error::Error; use std::fmt; @@ -556,6 +558,12 @@ mod tests { use std::sync::Arc; use std::sync::Mutex; + // TODO(ry) Sadly FuturesUnordered requires the current task to be set. So + // even though we are only using poll() in these tests and not Tokio, we must + // nevertheless run it in the tokio executor. Ideally run_in_task can be + // removed in the future. + use crate::core_isolate::tests::run_in_task; + struct MockLoader { pub loads: Arc>>, } @@ -716,13 +724,6 @@ mod tests { if (import.meta.url != 'file:///d.js') throw Error(); "#; - // TODO(ry) Sadly FuturesUnordered requires the current task to be set. So - // even though we are only using poll() in these tests and not Tokio, we must - // nevertheless run it in the tokio executor. Ideally run_in_task can be - // removed in the future. - use crate::isolate::tests::run_in_task; - use crate::isolate::StartupData; - #[test] fn test_recursive_load() { let loader = MockLoader::new(); @@ -744,7 +745,9 @@ mod tests { ] ); - let modules = &isolate.modules; + let state_rc = EsIsolate::state(&isolate); + let state = state_rc.borrow(); + let modules = &state.modules; assert_eq!(modules.get_id("file:///a.js"), Some(a_id)); let b_id = modules.get_id("file:///b.js").unwrap(); let c_id = modules.get_id("file:///c.js").unwrap(); @@ -806,7 +809,9 @@ mod tests { ] ); - let modules = &isolate.modules; + let state_rc = EsIsolate::state(&isolate); + let state = state_rc.borrow(); + let modules = &state.modules; assert_eq!(modules.get_id("file:///circular1.js"), Some(circular1_id)); let circular2_id = modules.get_id("file:///circular2.js").unwrap(); @@ -877,7 +882,9 @@ mod tests { ] ); - let modules = &isolate.modules; + let state_rc = EsIsolate::state(&isolate); + let state = state_rc.borrow(); + let modules = &state.modules; assert_eq!(modules.get_id("file:///redirect1.js"), Some(redirect1_id)); @@ -1015,7 +1022,9 @@ mod tests { vec!["file:///b.js", "file:///c.js", "file:///d.js"] ); - let modules = &isolate.modules; + let state_rc = EsIsolate::state(&isolate); + let state = state_rc.borrow(); + let modules = &state.modules; assert_eq!(modules.get_id("file:///main_with_code.js"), Some(main_id)); let b_id = modules.get_id("file:///b.js").unwrap(); diff --git a/core/ops.rs b/core/ops.rs index 0361f5ee9f1bce..bd9d58283787ab 100644 --- a/core/ops.rs +++ b/core/ops.rs @@ -1,5 +1,5 @@ // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. -use crate::CoreIsolate; +use crate::core_isolate::CoreIsolateState; use crate::ZeroCopyBuf; use futures::Future; use std::collections::HashMap; @@ -22,7 +22,7 @@ pub enum Op { /// Main type describing op pub type OpDispatcher = - dyn Fn(&mut CoreIsolate, &[u8], Option) -> Op + 'static; + dyn Fn(&mut CoreIsolateState, &[u8], &mut [ZeroCopyBuf]) -> Op + 'static; #[derive(Default)] pub struct OpRegistry { @@ -33,8 +33,8 @@ pub struct OpRegistry { impl OpRegistry { pub fn new() -> Self { let mut registry = Self::default(); - let op_id = registry.register("ops", |isolate, _, _| { - let buf = isolate.op_registry.json_map(); + let op_id = registry.register("ops", |state, _, _| { + let buf = state.op_registry.json_map(); Op::Sync(buf) }); assert_eq!(op_id, 0); @@ -43,7 +43,7 @@ impl OpRegistry { pub fn register(&mut self, name: &str, op: F) -> OpId where - F: Fn(&mut CoreIsolate, &[u8], Option) -> Op + 'static, + F: Fn(&mut CoreIsolateState, &[u8], &mut [ZeroCopyBuf]) -> Op + 'static, { let op_id = self.dispatchers.len() as u32; @@ -68,6 +68,7 @@ impl OpRegistry { #[test] fn test_op_registry() { + use crate::CoreIsolate; use std::sync::atomic; use std::sync::Arc; let mut op_registry = OpRegistry::new(); @@ -86,10 +87,12 @@ fn test_op_registry() { expected.insert("test".to_string(), 1); assert_eq!(op_registry.name_to_id, expected); - let mut isolate = CoreIsolate::new(crate::StartupData::None, false); + let isolate = CoreIsolate::new(crate::StartupData::None, false); let dispatch = op_registry.get(test_id).unwrap(); - let res = dispatch(&mut isolate, &[], None); + let state_rc = CoreIsolate::state(&isolate); + let mut state = state_rc.borrow_mut(); + let res = dispatch(&mut state, &[], &mut []); if let Op::Sync(buf) = res { assert_eq!(buf.len(), 0); } else { @@ -102,6 +105,7 @@ fn test_op_registry() { #[test] fn register_op_during_call() { + use crate::CoreIsolate; use std::sync::atomic; use std::sync::Arc; use std::sync::Mutex; @@ -126,13 +130,17 @@ fn register_op_during_call() { }; assert!(test_id != 0); - let mut isolate = CoreIsolate::new(crate::StartupData::None, false); + let isolate = CoreIsolate::new(crate::StartupData::None, false); let dispatcher1 = { let g = op_registry.lock().unwrap(); g.get(test_id).unwrap() }; - dispatcher1(&mut isolate, &[], None); + { + let state_rc = CoreIsolate::state(&isolate); + let mut state = state_rc.borrow_mut(); + dispatcher1(&mut state, &[], &mut []); + } let mut expected = HashMap::new(); expected.insert("ops".to_string(), 0); @@ -147,7 +155,9 @@ fn register_op_during_call() { let g = op_registry.lock().unwrap(); g.get(2).unwrap() }; - let res = dispatcher2(&mut isolate, &[], None); + let state_rc = CoreIsolate::state(&isolate); + let mut state = state_rc.borrow_mut(); + let res = dispatcher2(&mut state, &[], &mut []); if let Op::Sync(buf) = res { assert_eq!(buf.len(), 0); } else { diff --git a/core/plugin_api.rs b/core/plugin_api.rs index 2e93fdb77e5eec..16f5d4a365b6c4 100644 --- a/core/plugin_api.rs +++ b/core/plugin_api.rs @@ -15,8 +15,7 @@ pub use crate::ZeroCopyBuf; pub type InitFn = fn(&mut dyn Interface); -pub type DispatchOpFn = - fn(&mut dyn Interface, &[u8], Option) -> Op; +pub type DispatchOpFn = fn(&mut dyn Interface, &[u8], &mut [ZeroCopyBuf]) -> Op; pub trait Interface { fn register_op(&mut self, name: &str, dispatcher: DispatchOpFn) -> OpId; diff --git a/core/zero_copy_buf.rs b/core/zero_copy_buf.rs new file mode 100644 index 00000000000000..be61d5f9841892 --- /dev/null +++ b/core/zero_copy_buf.rs @@ -0,0 +1,71 @@ +use crate::bindings; +use rusty_v8 as v8; +use std::ops::Deref; +use std::ops::DerefMut; + +/// A ZeroCopyBuf encapsulates a slice that's been borrowed from a JavaScript +/// ArrayBuffer object. JavaScript objects can normally be garbage collected, +/// but the existence of a ZeroCopyBuf inhibits this until it is dropped. It +/// behaves much like an Arc<[u8]>, although a ZeroCopyBuf currently can't be +/// cloned. +#[derive(Clone)] +pub struct ZeroCopyBuf { + backing_store: v8::SharedRef, + byte_offset: usize, + byte_length: usize, +} + +unsafe impl Send for ZeroCopyBuf {} + +impl ZeroCopyBuf { + pub fn new<'s>( + scope: &mut impl v8::ToLocal<'s>, + view: v8::Local, + ) -> Self { + let backing_store = view.buffer(scope).unwrap().get_backing_store(); + let byte_offset = view.byte_offset(); + let byte_length = view.byte_length(); + Self { + backing_store, + byte_offset, + byte_length, + } + } +} + +impl Deref for ZeroCopyBuf { + type Target = [u8]; + fn deref(&self) -> &[u8] { + unsafe { + bindings::get_backing_store_slice( + &self.backing_store, + self.byte_offset, + self.byte_length, + ) + } + } +} + +impl DerefMut for ZeroCopyBuf { + fn deref_mut(&mut self) -> &mut [u8] { + unsafe { + bindings::get_backing_store_slice_mut( + &self.backing_store, + self.byte_offset, + self.byte_length, + ) + } + } +} + +impl AsRef<[u8]> for ZeroCopyBuf { + fn as_ref(&self) -> &[u8] { + &*self + } +} + +impl AsMut<[u8]> for ZeroCopyBuf { + fn as_mut(&mut self) -> &mut [u8] { + &mut *self + } +} diff --git a/deno_typescript/Cargo.toml b/deno_typescript/Cargo.toml index 66a278576fe1e0..50802ce423160c 100644 --- a/deno_typescript/Cargo.toml +++ b/deno_typescript/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "deno_typescript" -version = "0.45.2" +version = "0.47.1" license = "MIT" description = "To compile TypeScript to a snapshot during build.rs" repository = "https://github.com/denoland/deno" @@ -19,6 +19,6 @@ exclude = [ path = "lib.rs" [dependencies] -deno_core = { path = "../core", version = "0.45.0" } -serde_json = "1.0.52" -serde = { version = "1.0.106", features = ["derive"] } +deno_core = { path = "../core", version = "0.47.1" } +serde_json = "1.0.53" +serde = { version = "1.0.111", features = ["derive"] } diff --git a/deno_typescript/compiler_main.js b/deno_typescript/compiler_main.js index 31f539a2753cb9..847f3435f65f2a 100644 --- a/deno_typescript/compiler_main.js +++ b/deno_typescript/compiler_main.js @@ -5,6 +5,8 @@ // understood by the TypeScript language service, so it allows type safety // checking in VSCode. +"use strict"; + const ASSETS = "$asset$"; /** diff --git a/deno_typescript/lib.rs b/deno_typescript/lib.rs index 590e2991713fd6..52fb00b767c8b6 100644 --- a/deno_typescript/lib.rs +++ b/deno_typescript/lib.rs @@ -9,6 +9,7 @@ mod ops; use deno_core::js_check; pub use deno_core::v8_set_flags; use deno_core::CoreIsolate; +use deno_core::CoreIsolateState; use deno_core::ErrBox; use deno_core::ModuleSpecifier; use deno_core::Op; @@ -49,22 +50,22 @@ pub struct TSState { fn compiler_op( ts_state: Arc>, dispatcher: D, -) -> impl Fn(&mut CoreIsolate, &[u8], Option) -> Op +) -> impl Fn(&mut CoreIsolateState, &[u8], &mut [ZeroCopyBuf]) -> Op where D: Fn(&mut TSState, &[u8]) -> Op, { - move |_isolate: &mut CoreIsolate, + move |_state: &mut CoreIsolateState, control: &[u8], - zero_copy_buf: Option| + zero_copy_bufs: &mut [ZeroCopyBuf]| -> Op { - assert!(zero_copy_buf.is_none()); // zero_copy_buf unused in compiler. + assert!(zero_copy_bufs.is_empty()); // zero_copy_bufs unused in compiler. let mut s = ts_state.lock().unwrap(); dispatcher(&mut s, control) } } pub struct TSIsolate { - isolate: Box, + isolate: CoreIsolate, state: Arc>, } @@ -331,15 +332,15 @@ pub fn trace_serializer() { /// CoreIsolate. pub fn op_fetch_asset( custom_assets: HashMap, -) -> impl Fn(&mut CoreIsolate, &[u8], Option) -> Op { +) -> impl Fn(&mut CoreIsolateState, &[u8], &mut [ZeroCopyBuf]) -> Op { for (_, path) in custom_assets.iter() { println!("cargo:rerun-if-changed={}", path.display()); } - move |_isolate: &mut CoreIsolate, + move |_state: &mut CoreIsolateState, control: &[u8], - zero_copy_buf: Option| + zero_copy_bufs: &mut [ZeroCopyBuf]| -> Op { - assert!(zero_copy_buf.is_none()); // zero_copy_buf unused in this op. + assert!(zero_copy_bufs.is_empty()); // zero_copy_bufs unused in this op. let name = std::str::from_utf8(control).unwrap(); let asset_code = if let Some(source_code) = get_asset(name) { diff --git a/deno_typescript/system_loader.js b/deno_typescript/system_loader.js index 0004d055d62cb0..fdf1fa87230f41 100644 --- a/deno_typescript/system_loader.js +++ b/deno_typescript/system_loader.js @@ -2,6 +2,8 @@ // This is a specialised implementation of a System module loader. +"use strict"; + // @ts-nocheck /* eslint-disable */ let System, __instantiateAsync, __instantiate; diff --git a/docs/contributing.md b/docs/contributing.md index b48d18450771e6..4d1d75cf522fb3 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -45,6 +45,11 @@ Please list how this functionality is done in Go, Node, Rust, and Python. As an example, see how `Deno.rename()` was proposed and added in [PR #671](https://github.com/denoland/deno/pull/671). +## Releases + +Summary of the changes from previous releases can be found +[here](https://github.com/denoland/deno/releases). + ## Documenting APIs It is important to document public APIs and we want to do that inline with the diff --git a/docs/getting_started/setup_your_environment.md b/docs/getting_started/setup_your_environment.md index f916331bcd7951..8baa13dd5f7f74 100644 --- a/docs/getting_started/setup_your_environment.md +++ b/docs/getting_started/setup_your_environment.md @@ -69,6 +69,36 @@ server protocol). After CoC itself is installed, from inside Vim, simply run `:CocInstall coc-deno`. From now on, things like `gd` (go to definition) and `gr` (goto/find references) should work. +#### Emacs + +Emacs works pretty well for a TypeScript project targeted to Deno by using a +combination of [tide](https://github.com/ananthakumaran/tide) which is the +canonical way of using TypeScript within Emacs and +[typescript-deno-plugin](https://github.com/justjavac/typescript-deno-plugin) +which is what is used by the +[official VSCode extension for Deno](https://github.com/denoland/vscode_deno). + +To use it, first make sure that `tide` is setup for your instance of Emacs. +Next, as instructed on the +[typescript-deno-plugin](https://github.com/justjavac/typescript-deno-plugin) +page, first `npm install --save-dev typescript-deno-plugin typescript` in your +project (`npm init -y` as necessary), then add the following block to your +`tsconfig.json` and you are off to the races! + +```json +{ + "compilerOptions": { + "plugins": [ + { + "name": "typescript-deno-plugin", + "enable": true, // default is `true` + "importmap": "import_map.json" + } + ] + } +} +``` + If you don't see your favorite IDE on this list, maybe you can develop an extension. Our [community Discord group](https://discord.gg/TGMHGv6) can give you some pointers on where to get started. diff --git a/docs/tools/documentation_generator.md b/docs/tools/documentation_generator.md index 07b0b5c953e382..661fa22e5d200c 100644 --- a/docs/tools/documentation_generator.md +++ b/docs/tools/documentation_generator.md @@ -1,3 +1,33 @@ ## Documentation Generator - +`deno doc` followed by a list of one or more source files will print the JSDoc +documentation for each of the module's **exported** members. Currently, only +exports in the style `export ` and `export ... from ...` are are +supported. + +For example, given a file `add.ts` with the contents: + +```ts +/** + * Adds x and y. + * @param {number} x + * @param {number} y + * @returns {number} Sum of x and y + */ +export function add(x: number, y: number): number { + return x + y; +} +``` + +Running the Deno `doc` command, prints the function's JSDoc comment to `stdout`: + +```shell +deno doc add.ts +function add(x: number, y: number): number + Adds x and y. @param {number} x @param {number} y @returns {number} Sum of x and y +``` + +Use the `--json` flag to output the documentation in JSON format. This JSON +format is consumed by the +[deno doc website](https://github.com/denoland/doc_website) and used to generate +module documentation. diff --git a/std/encoding/README.md b/std/encoding/README.md index c830133ba12ccd..50db5364f94b3b 100644 --- a/std/encoding/README.md +++ b/std/encoding/README.md @@ -29,8 +29,50 @@ writeVarbig(w: Deno.Writer, x: bigint, o: VarbigOptions = {}): Promise ## CSV -- **`parse(input: string | BufReader, opt: ParseCsvOptions): Promise`**: - Read the string/buffer into an +### API + +#### `readMatrix(reader: BufReader, opt: ReadOptions = { comma: ",", trimLeadingSpace: false, lazyQuotes: false }): Promise` + +Parse the CSV from the `reader` with the options provided and return +`string[][]`. + +#### `parse(input: string | BufReader, opt: ParseOptions = { header: false }): Promise`: + +Parse the CSV string/buffer with the options provided. The result of this +function is as follows: + +- If you don't provide both `opt.header` and `opt.parse`, it returns + `string[][]`. +- If you provide `opt.header` but not `opt.parse`, it returns `object[]`. +- If you provide `opt.parse`, it returns an array where each element is the + value returned from `opt.parse`. + +##### `ParseOptions` + +- **`header: boolean | string[] | HeaderOptions[];`**: If a boolean is provided, + the first line will be used as Header definitions. If `string[]` or + `HeaderOptions[]` those names will be used for header definition. +- **`parse?: (input: unknown) => unknown;`**: Parse function for the row, which + will be executed after parsing of all columns. Therefore if you don't provide + header and parse function with headers, input will be `string[]`. + +##### `HeaderOptions` + +- **`name: string;`**: Name of the header to be used as property. +- **`parse?: (input: string) => unknown;`**: Parse function for the column. This + is executed on each entry of the header. This can be combined with the Parse + function of the rows. + +##### `ReadOptions` + +- **`comma?: string;`**: Character which separates values. Default: `','` +- **`comment?: string;`**: Character to start a comment. Default: `'#'` +- **`trimLeadingSpace?: boolean;`**: Flag to trim the leading space of the + value. Default: `false` +- **`lazyQuotes?: boolean;`**: Allow unquoted quote in a quoted field or non + double quoted quotes in quoted field. Default: 'false` +- **`fieldsPerRecord?`**: Enabling the check of fields for each row. If == 0, + first row is used as referral for the number of fields. ### Usage diff --git a/std/encoding/base64url.ts b/std/encoding/base64url.ts new file mode 100644 index 00000000000000..726ea2eb898f66 --- /dev/null +++ b/std/encoding/base64url.ts @@ -0,0 +1,45 @@ +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. + +import { + decode as convertBase64ToArrayBuffer, + encode as convertArrayBufferToBase64, +} from "./base64.ts"; + +/* + * Some variants allow or require omitting the padding '=' signs: + * https://en.wikipedia.org/wiki/Base64#URL_applications + */ +export function addPaddingToBase64url(base64url: string): string { + if (base64url.length % 4 === 2) return base64url + "=="; + if (base64url.length % 4 === 3) return base64url + "="; + if (base64url.length % 4 === 1) + throw new TypeError("Illegal base64url string!"); + return base64url; +} + +function convertBase64urlToBase64(base64url: string): string { + return addPaddingToBase64url(base64url) + .replace(/\-/g, "+") + .replace(/_/g, "/"); +} + +function convertBase64ToBase64url(base64: string): string { + return base64.replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_"); +} + +/** + * Converts given data with base64url encoding. + * Removes paddings '='. + * @param data input to encode + */ +export function encode(data: string | ArrayBuffer): string { + return convertBase64ToBase64url(convertArrayBufferToBase64(data)); +} + +/** + * Converts given base64url encoded data back to original + * @param data input to decode + */ +export function decode(data: string): ArrayBuffer { + return convertBase64ToArrayBuffer(convertBase64urlToBase64(data)); +} diff --git a/std/encoding/base64url_test.ts b/std/encoding/base64url_test.ts new file mode 100644 index 00000000000000..2af9096a41bf93 --- /dev/null +++ b/std/encoding/base64url_test.ts @@ -0,0 +1,42 @@ +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. + +const { test } = Deno; +import { assertEquals } from "../testing/asserts.ts"; +import { encode, decode } from "./base64url.ts"; + +const testsetString = [ + ["", ""], + ["f", "Zg"], + ["fo", "Zm8"], + ["foo", "Zm9v"], + ["foob", "Zm9vYg"], + ["fooba", "Zm9vYmE"], + ["foobar", "Zm9vYmFy"], + [">?>d?ß", "Pj8-ZD_f"], +]; + +const testsetBinary = [ + [new TextEncoder().encode("\x00"), "AA"], + [new TextEncoder().encode("\x00\x00"), "AAA"], + [new TextEncoder().encode("\x00\x00\x00"), "AAAA"], + [new TextEncoder().encode("\x00\x00\x00\x00"), "AAAAAA"], +]; + +test("[encoding/base64url] testBase64urlEncodeString", () => { + for (const [input, output] of testsetString) { + assertEquals(encode(input), output); + } +}); + +test("[encoding/base64url] testBase64urlEncodeBinary", () => { + for (const [input, output] of testsetBinary) { + assertEquals(encode(input), output); + } +}); + +test("[encoding/base64ur] testBase64urDecodeBinary", () => { + for (const [input, output] of testsetBinary) { + const outputBinary = new Uint8Array(decode(output as string)); + assertEquals(outputBinary, input as Uint8Array); + } +}); diff --git a/std/encoding/csv.ts b/std/encoding/csv.ts index 3040d492c0331a..3e7164cbb9f406 100644 --- a/std/encoding/csv.ts +++ b/std/encoding/csv.ts @@ -32,7 +32,7 @@ export class ParseError extends Error { * @property trimLeadingSpace - Flag to trim the leading space of the value. * Default: 'false' * @property lazyQuotes - Allow unquoted quote in a quoted field or non double - * quoted quotes in quoted field Default: 'false' + * quoted quotes in quoted field. Default: 'false' * @property fieldsPerRecord - Enabling the check of fields for each row. * If == 0, first row is used as referral for the number of fields. */ @@ -209,6 +209,12 @@ async function readLine(tp: TextProtoReader): Promise { return line; } +/** + * Parse the CSV from the `reader` with the options provided and return `string[][]`. + * + * @param reader provides the CSV data to parse + * @param opt controls the parsing behavior + */ export async function readMatrix( reader: BufReader, opt: ReadOptions = { @@ -253,16 +259,30 @@ export async function readMatrix( } /** + * Parse the CSV string/buffer with the options provided. + * * HeaderOptions provides the column definition * and the parse function for each entry of the * column. */ export interface HeaderOptions { + /** + * Name of the header to be used as property + */ name: string; + /** + * Parse function for the column. + * This is executed on each entry of the header. + * This can be combined with the Parse function of the rows. + */ parse?: (input: string) => unknown; } export interface ParseOptions extends ReadOptions { + /** + * If a boolean is provided, the first line will be used as Header definitions. + * If `string[]` or `HeaderOptions[]` those names will be used for header definition. + */ header: boolean | string[] | HeaderOptions[]; /** Parse function for rows. * Example: @@ -287,6 +307,9 @@ export interface ParseOptions extends ReadOptions { * for columns and rows. * @param input Input to parse. Can be a string or BufReader. * @param opt options of the parser. + * @returns If you don't provide both `opt.header` and `opt.parse`, it returns `string[][]`. + * If you provide `opt.header` but not `opt.parse`, it returns `object[]`. + * If you provide `opt.parse`, it returns an array where each element is the value returned from `opt.parse`. */ export async function parse( input: string | BufReader, diff --git a/std/encoding/csv_test.ts b/std/encoding/csv_test.ts index b3d4ec0c9c9f9a..60d43e35e65e0f 100644 --- a/std/encoding/csv_test.ts +++ b/std/encoding/csv_test.ts @@ -385,7 +385,6 @@ x,,, "#ignore\n".repeat(10000) + "@".repeat(5000) + "," + "*".repeat(5000), Output: [["@".repeat(5000), "*".repeat(5000)]], Comment: "#", - ignore: true, // TODO(#4521) }, { Name: "QuoteWithTrailingCRLF", @@ -455,7 +454,6 @@ x,,, ]; for (const t of testCases) { Deno.test({ - ignore: !!t.ignore, name: `[CSV] ${t.Name}`, async fn(): Promise { let comma = ","; diff --git a/std/examples/catj.ts b/std/examples/catj.ts index bb2e9051b633b5..93fc68a1d68cdf 100644 --- a/std/examples/catj.ts +++ b/std/examples/catj.ts @@ -1,4 +1,4 @@ -#!/usr/bin/env -S deno --allow-read +#!/usr/bin/env -S deno run --allow-read // Ported from: https://github.com/soheilpro/catj // Copyright (c) 2014 Soheil Rashidi // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. diff --git a/std/examples/gist.ts b/std/examples/gist.ts index 5d66dbdafed7a7..555ec8056485ad 100755 --- a/std/examples/gist.ts +++ b/std/examples/gist.ts @@ -1,4 +1,4 @@ -#!/usr/bin/env -S deno --allow-net --allow-env +#!/usr/bin/env -S deno run --allow-net --allow-env // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. // A program to post files to gist.github.com. Use the following to install it: // deno install -f --allow-env --allow-read --allow-net=api.github.com https://deno.land/std/examples/gist.ts diff --git a/std/hash/_sha3/keccak.ts b/std/hash/_sha3/keccak.ts new file mode 100644 index 00000000000000..67cccca3c0e7b2 --- /dev/null +++ b/std/hash/_sha3/keccak.ts @@ -0,0 +1,52 @@ +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. + +import { Sponge } from "./sponge.ts"; +import { keccakf } from "./keccakf.ts"; + +/** Keccak-224 hash */ +export class Keccak224 extends Sponge { + constructor() { + super({ + bitsize: 224, + rate: 144, + dsbyte: 1, + permutator: keccakf, + }); + } +} + +/** Keccak-256 hash */ +export class Keccak256 extends Sponge { + constructor() { + super({ + bitsize: 256, + rate: 136, + dsbyte: 1, + permutator: keccakf, + }); + } +} + +/** Keccak-384 hash */ +export class Keccak384 extends Sponge { + constructor() { + super({ + bitsize: 384, + rate: 104, + dsbyte: 1, + permutator: keccakf, + }); + } +} + +/** Keccak-512 hash */ +export class Keccak512 extends Sponge { + constructor() { + super({ + bitsize: 512, + rate: 72, + dsbyte: 1, + permutator: keccakf, + }); + } +} diff --git a/std/hash/_sha3/keccakf.ts b/std/hash/_sha3/keccakf.ts new file mode 100644 index 00000000000000..8db90b1a4b4eae --- /dev/null +++ b/std/hash/_sha3/keccakf.ts @@ -0,0 +1,790 @@ +// Ported from Go: +// https://github.com/golang/crypto/blob/master/sha3/keccakf.go +// Copyright 2011 The Go Authors. All rights reserved. BSD license. +// https://github.com/golang/go/blob/master/LICENSE +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. + +const KECCAK_ROUNDS = 24; +const KECCAK_RC: number[] = [ + 0x1, + 0x0, + 0x8082, + 0x0, + 0x808a, + 0x80000000, + 0x80008000, + 0x80000000, + 0x808b, + 0x0, + 0x80000001, + 0x0, + 0x80008081, + 0x80000000, + 0x8009, + 0x80000000, + 0x8a, + 0x0, + 0x88, + 0x0, + 0x80008009, + 0x0, + 0x8000000a, + 0x0, + 0x8000808b, + 0x0, + 0x8b, + 0x80000000, + 0x8089, + 0x80000000, + 0x8003, + 0x80000000, + 0x8002, + 0x80000000, + 0x80, + 0x80000000, + 0x800a, + 0x0, + 0x8000000a, + 0x80000000, + 0x80008081, + 0x80000000, + 0x8080, + 0x80000000, + 0x80000001, + 0x0, + 0x80008008, + 0x80000000, +]; + +/** keccak1600 permutation function */ +export function keccakf(state: Uint8Array): void { + const s = new Uint32Array(state.buffer); + let bc0 = 0; + let bc1 = 0; + let bc2 = 0; + let bc3 = 0; + let bc4 = 0; + let bc5 = 0; + let bc6 = 0; + let bc7 = 0; + let bc8 = 0; + let bc9 = 0; + let d0 = 0; + let d1 = 0; + let d2 = 0; + let d3 = 0; + let d4 = 0; + let d5 = 0; + let d6 = 0; + let d7 = 0; + let d8 = 0; + let d9 = 0; + let t0 = 0; + let t1 = 0; + + for (let n = 0; n < KECCAK_ROUNDS * 2; n += 8) { + // Round 1 + bc0 = s[0] ^ s[10] ^ s[20] ^ s[30] ^ s[40]; + bc1 = s[1] ^ s[11] ^ s[21] ^ s[31] ^ s[41]; + bc2 = s[2] ^ s[12] ^ s[22] ^ s[32] ^ s[42]; + bc3 = s[3] ^ s[13] ^ s[23] ^ s[33] ^ s[43]; + bc4 = s[4] ^ s[14] ^ s[24] ^ s[34] ^ s[44]; + bc5 = s[5] ^ s[15] ^ s[25] ^ s[35] ^ s[45]; + bc6 = s[6] ^ s[16] ^ s[26] ^ s[36] ^ s[46]; + bc7 = s[7] ^ s[17] ^ s[27] ^ s[37] ^ s[47]; + bc8 = s[8] ^ s[18] ^ s[28] ^ s[38] ^ s[48]; + bc9 = s[9] ^ s[19] ^ s[29] ^ s[39] ^ s[49]; + + d0 = bc8 ^ ((bc2 << 1) | (bc3 >>> 31)); + d1 = bc9 ^ ((bc3 << 1) | (bc2 >>> 31)); + d2 = bc0 ^ ((bc4 << 1) | (bc5 >>> 31)); + d3 = bc1 ^ ((bc5 << 1) | (bc4 >>> 31)); + d4 = bc2 ^ ((bc6 << 1) | (bc7 >>> 31)); + d5 = bc3 ^ ((bc7 << 1) | (bc6 >>> 31)); + d6 = bc4 ^ ((bc8 << 1) | (bc9 >>> 31)); + d7 = bc5 ^ ((bc9 << 1) | (bc8 >>> 31)); + d8 = bc6 ^ ((bc0 << 1) | (bc1 >>> 31)); + d9 = bc7 ^ ((bc1 << 1) | (bc0 >>> 31)); + + bc0 = s[0] ^ d0; + bc1 = s[1] ^ d1; + t0 = s[12] ^ d2; + t1 = s[13] ^ d3; + bc2 = (t1 << 12) | (t0 >>> 20); + bc3 = (t0 << 12) | (t1 >>> 20); + t0 = s[24] ^ d4; + t1 = s[25] ^ d5; + bc4 = (t1 << 11) | (t0 >>> 21); + bc5 = (t0 << 11) | (t1 >>> 21); + t0 = s[36] ^ d6; + t1 = s[37] ^ d7; + bc6 = (t0 << 21) | (t1 >>> 11); + bc7 = (t1 << 21) | (t0 >>> 11); + t0 = s[48] ^ d8; + t1 = s[49] ^ d9; + bc8 = (t0 << 14) | (t1 >>> 18); + bc9 = (t1 << 14) | (t0 >>> 18); + s[0] = bc0 ^ (bc4 & ~bc2) ^ KECCAK_RC[n]; + s[1] = bc1 ^ (bc5 & ~bc3) ^ KECCAK_RC[n + 1]; + s[12] = bc2 ^ (bc6 & ~bc4); + s[13] = bc3 ^ (bc7 & ~bc5); + s[24] = bc4 ^ (bc8 & ~bc6); + s[25] = bc5 ^ (bc9 & ~bc7); + s[36] = bc6 ^ (bc0 & ~bc8); + s[37] = bc7 ^ (bc1 & ~bc9); + s[48] = bc8 ^ (bc2 & ~bc0); + s[49] = bc9 ^ (bc3 & ~bc1); + + t0 = s[20] ^ d0; + t1 = s[21] ^ d1; + bc4 = (t0 << 3) | (t1 >>> 29); + bc5 = (t1 << 3) | (t0 >>> 29); + t0 = s[32] ^ d2; + t1 = s[33] ^ d3; + bc6 = (t1 << 13) | (t0 >>> 19); + bc7 = (t0 << 13) | (t1 >>> 19); + t0 = s[44] ^ d4; + t1 = s[45] ^ d5; + bc8 = (t1 << 29) | (t0 >>> 3); + bc9 = (t0 << 29) | (t1 >>> 3); + t0 = s[6] ^ d6; + t1 = s[7] ^ d7; + bc0 = (t0 << 28) | (t1 >>> 4); + bc1 = (t1 << 28) | (t0 >>> 4); + t0 = s[18] ^ d8; + t1 = s[19] ^ d9; + bc2 = (t0 << 20) | (t1 >>> 12); + bc3 = (t1 << 20) | (t0 >>> 12); + s[20] = bc0 ^ (bc4 & ~bc2); + s[21] = bc1 ^ (bc5 & ~bc3); + s[32] = bc2 ^ (bc6 & ~bc4); + s[33] = bc3 ^ (bc7 & ~bc5); + s[44] = bc4 ^ (bc8 & ~bc6); + s[45] = bc5 ^ (bc9 & ~bc7); + s[6] = bc6 ^ (bc0 & ~bc8); + s[7] = bc7 ^ (bc1 & ~bc9); + s[18] = bc8 ^ (bc2 & ~bc0); + s[19] = bc9 ^ (bc3 & ~bc1); + + t0 = s[40] ^ d0; + t1 = s[41] ^ d1; + bc8 = (t0 << 18) | (t1 >>> 14); + bc9 = (t1 << 18) | (t0 >>> 14); + t0 = s[2] ^ d2; + t1 = s[3] ^ d3; + bc0 = (t0 << 1) | (t1 >>> 31); + bc1 = (t1 << 1) | (t0 >>> 31); + t0 = s[14] ^ d4; + t1 = s[15] ^ d5; + bc2 = (t0 << 6) | (t1 >>> 26); + bc3 = (t1 << 6) | (t0 >>> 26); + t0 = s[26] ^ d6; + t1 = s[27] ^ d7; + bc4 = (t0 << 25) | (t1 >>> 7); + bc5 = (t1 << 25) | (t0 >>> 7); + t0 = s[38] ^ d8; + t1 = s[39] ^ d9; + bc6 = (t0 << 8) | (t1 >>> 24); + bc7 = (t1 << 8) | (t0 >>> 24); + s[40] = bc0 ^ (bc4 & ~bc2); + s[41] = bc1 ^ (bc5 & ~bc3); + s[2] = bc2 ^ (bc6 & ~bc4); + s[3] = bc3 ^ (bc7 & ~bc5); + s[14] = bc4 ^ (bc8 & ~bc6); + s[15] = bc5 ^ (bc9 & ~bc7); + s[26] = bc6 ^ (bc0 & ~bc8); + s[27] = bc7 ^ (bc1 & ~bc9); + s[38] = bc8 ^ (bc2 & ~bc0); + s[39] = bc9 ^ (bc3 & ~bc1); + + t0 = s[10] ^ d0; + t1 = s[11] ^ d1; + bc2 = (t1 << 4) | (t0 >>> 28); + bc3 = (t0 << 4) | (t1 >>> 28); + t0 = s[22] ^ d2; + t1 = s[23] ^ d3; + bc4 = (t0 << 10) | (t1 >>> 22); + bc5 = (t1 << 10) | (t0 >>> 22); + t0 = s[34] ^ d4; + t1 = s[35] ^ d5; + bc6 = (t0 << 15) | (t1 >>> 17); + bc7 = (t1 << 15) | (t0 >>> 17); + t0 = s[46] ^ d6; + t1 = s[47] ^ d7; + bc8 = (t1 << 24) | (t0 >>> 8); + bc9 = (t0 << 24) | (t1 >>> 8); + t0 = s[8] ^ d8; + t1 = s[9] ^ d9; + bc0 = (t0 << 27) | (t1 >>> 5); + bc1 = (t1 << 27) | (t0 >>> 5); + s[10] = bc0 ^ (bc4 & ~bc2); + s[11] = bc1 ^ (bc5 & ~bc3); + s[22] = bc2 ^ (bc6 & ~bc4); + s[23] = bc3 ^ (bc7 & ~bc5); + s[34] = bc4 ^ (bc8 & ~bc6); + s[35] = bc5 ^ (bc9 & ~bc7); + s[46] = bc6 ^ (bc0 & ~bc8); + s[47] = bc7 ^ (bc1 & ~bc9); + s[8] = bc8 ^ (bc2 & ~bc0); + s[9] = bc9 ^ (bc3 & ~bc1); + + t0 = s[30] ^ d0; + t1 = s[31] ^ d1; + bc6 = (t1 << 9) | (t0 >>> 23); + bc7 = (t0 << 9) | (t1 >>> 23); + t0 = s[42] ^ d2; + t1 = s[43] ^ d3; + bc8 = (t0 << 2) | (t1 >>> 30); + bc9 = (t1 << 2) | (t0 >>> 30); + t0 = s[4] ^ d4; + t1 = s[5] ^ d5; + bc0 = (t1 << 30) | (t0 >>> 2); + bc1 = (t0 << 30) | (t1 >>> 2); + t0 = s[16] ^ d6; + t1 = s[17] ^ d7; + bc2 = (t1 << 23) | (t0 >>> 9); + bc3 = (t0 << 23) | (t1 >>> 9); + t0 = s[28] ^ d8; + t1 = s[29] ^ d9; + bc4 = (t1 << 7) | (t0 >>> 25); + bc5 = (t0 << 7) | (t1 >>> 25); + s[30] = bc0 ^ (bc4 & ~bc2); + s[31] = bc1 ^ (bc5 & ~bc3); + s[42] = bc2 ^ (bc6 & ~bc4); + s[43] = bc3 ^ (bc7 & ~bc5); + s[4] = bc4 ^ (bc8 & ~bc6); + s[5] = bc5 ^ (bc9 & ~bc7); + s[16] = bc6 ^ (bc0 & ~bc8); + s[17] = bc7 ^ (bc1 & ~bc9); + s[28] = bc8 ^ (bc2 & ~bc0); + s[29] = bc9 ^ (bc3 & ~bc1); + + // Round 2 + bc0 = s[0] ^ s[10] ^ s[20] ^ s[30] ^ s[40]; + bc1 = s[1] ^ s[11] ^ s[21] ^ s[31] ^ s[41]; + bc2 = s[2] ^ s[12] ^ s[22] ^ s[32] ^ s[42]; + bc3 = s[3] ^ s[13] ^ s[23] ^ s[33] ^ s[43]; + bc4 = s[4] ^ s[14] ^ s[24] ^ s[34] ^ s[44]; + bc5 = s[5] ^ s[15] ^ s[25] ^ s[35] ^ s[45]; + bc6 = s[6] ^ s[16] ^ s[26] ^ s[36] ^ s[46]; + bc7 = s[7] ^ s[17] ^ s[27] ^ s[37] ^ s[47]; + bc8 = s[8] ^ s[18] ^ s[28] ^ s[38] ^ s[48]; + bc9 = s[9] ^ s[19] ^ s[29] ^ s[39] ^ s[49]; + + d0 = bc8 ^ ((bc2 << 1) | (bc3 >>> 31)); + d1 = bc9 ^ ((bc3 << 1) | (bc2 >>> 31)); + d2 = bc0 ^ ((bc4 << 1) | (bc5 >>> 31)); + d3 = bc1 ^ ((bc5 << 1) | (bc4 >>> 31)); + d4 = bc2 ^ ((bc6 << 1) | (bc7 >>> 31)); + d5 = bc3 ^ ((bc7 << 1) | (bc6 >>> 31)); + d6 = bc4 ^ ((bc8 << 1) | (bc9 >>> 31)); + d7 = bc5 ^ ((bc9 << 1) | (bc8 >>> 31)); + d8 = bc6 ^ ((bc0 << 1) | (bc1 >>> 31)); + d9 = bc7 ^ ((bc1 << 1) | (bc0 >>> 31)); + + bc0 = s[0] ^ d0; + bc1 = s[1] ^ d1; + t0 = s[32] ^ d2; + t1 = s[33] ^ d3; + bc2 = (t1 << 12) | (t0 >>> 20); + bc3 = (t0 << 12) | (t1 >>> 20); + t0 = s[14] ^ d4; + t1 = s[15] ^ d5; + bc4 = (t1 << 11) | (t0 >>> 21); + bc5 = (t0 << 11) | (t1 >>> 21); + t0 = s[46] ^ d6; + t1 = s[47] ^ d7; + bc6 = (t0 << 21) | (t1 >>> 11); + bc7 = (t1 << 21) | (t0 >>> 11); + t0 = s[28] ^ d8; + t1 = s[29] ^ d9; + bc8 = (t0 << 14) | (t1 >>> 18); + bc9 = (t1 << 14) | (t0 >>> 18); + s[0] = bc0 ^ (bc4 & ~bc2) ^ KECCAK_RC[n + 2]; + s[1] = bc1 ^ (bc5 & ~bc3) ^ KECCAK_RC[n + 3]; + s[32] = bc2 ^ (bc6 & ~bc4); + s[33] = bc3 ^ (bc7 & ~bc5); + s[14] = bc4 ^ (bc8 & ~bc6); + s[15] = bc5 ^ (bc9 & ~bc7); + s[46] = bc6 ^ (bc0 & ~bc8); + s[47] = bc7 ^ (bc1 & ~bc9); + s[28] = bc8 ^ (bc2 & ~bc0); + s[29] = bc9 ^ (bc3 & ~bc1); + + t0 = s[40] ^ d0; + t1 = s[41] ^ d1; + bc4 = (t0 << 3) | (t1 >>> 29); + bc5 = (t1 << 3) | (t0 >>> 29); + t0 = s[22] ^ d2; + t1 = s[23] ^ d3; + bc6 = (t1 << 13) | (t0 >>> 19); + bc7 = (t0 << 13) | (t1 >>> 19); + t0 = s[4] ^ d4; + t1 = s[5] ^ d5; + bc8 = (t1 << 29) | (t0 >>> 3); + bc9 = (t0 << 29) | (t1 >>> 3); + t0 = s[36] ^ d6; + t1 = s[37] ^ d7; + bc0 = (t0 << 28) | (t1 >>> 4); + bc1 = (t1 << 28) | (t0 >>> 4); + t0 = s[18] ^ d8; + t1 = s[19] ^ d9; + bc2 = (t0 << 20) | (t1 >>> 12); + bc3 = (t1 << 20) | (t0 >>> 12); + s[40] = bc0 ^ (bc4 & ~bc2); + s[41] = bc1 ^ (bc5 & ~bc3); + s[22] = bc2 ^ (bc6 & ~bc4); + s[23] = bc3 ^ (bc7 & ~bc5); + s[4] = bc4 ^ (bc8 & ~bc6); + s[5] = bc5 ^ (bc9 & ~bc7); + s[36] = bc6 ^ (bc0 & ~bc8); + s[37] = bc7 ^ (bc1 & ~bc9); + s[18] = bc8 ^ (bc2 & ~bc0); + s[19] = bc9 ^ (bc3 & ~bc1); + + t0 = s[30] ^ d0; + t1 = s[31] ^ d1; + bc8 = (t0 << 18) | (t1 >>> 14); + bc9 = (t1 << 18) | (t0 >>> 14); + t0 = s[12] ^ d2; + t1 = s[13] ^ d3; + bc0 = (t0 << 1) | (t1 >>> 31); + bc1 = (t1 << 1) | (t0 >>> 31); + t0 = s[44] ^ d4; + t1 = s[45] ^ d5; + bc2 = (t0 << 6) | (t1 >>> 26); + bc3 = (t1 << 6) | (t0 >>> 26); + t0 = s[26] ^ d6; + t1 = s[27] ^ d7; + bc4 = (t0 << 25) | (t1 >>> 7); + bc5 = (t1 << 25) | (t0 >>> 7); + t0 = s[8] ^ d8; + t1 = s[9] ^ d9; + bc6 = (t0 << 8) | (t1 >>> 24); + bc7 = (t1 << 8) | (t0 >>> 24); + s[30] = bc0 ^ (bc4 & ~bc2); + s[31] = bc1 ^ (bc5 & ~bc3); + s[12] = bc2 ^ (bc6 & ~bc4); + s[13] = bc3 ^ (bc7 & ~bc5); + s[44] = bc4 ^ (bc8 & ~bc6); + s[45] = bc5 ^ (bc9 & ~bc7); + s[26] = bc6 ^ (bc0 & ~bc8); + s[27] = bc7 ^ (bc1 & ~bc9); + s[8] = bc8 ^ (bc2 & ~bc0); + s[9] = bc9 ^ (bc3 & ~bc1); + + t0 = s[20] ^ d0; + t1 = s[21] ^ d1; + bc2 = (t1 << 4) | (t0 >>> 28); + bc3 = (t0 << 4) | (t1 >>> 28); + t0 = s[2] ^ d2; + t1 = s[3] ^ d3; + bc4 = (t0 << 10) | (t1 >>> 22); + bc5 = (t1 << 10) | (t0 >>> 22); + t0 = s[34] ^ d4; + t1 = s[35] ^ d5; + bc6 = (t0 << 15) | (t1 >>> 17); + bc7 = (t1 << 15) | (t0 >>> 17); + t0 = s[16] ^ d6; + t1 = s[17] ^ d7; + bc8 = (t1 << 24) | (t0 >>> 8); + bc9 = (t0 << 24) | (t1 >>> 8); + t0 = s[48] ^ d8; + t1 = s[49] ^ d9; + bc0 = (t0 << 27) | (t1 >>> 5); + bc1 = (t1 << 27) | (t0 >>> 5); + s[20] = bc0 ^ (bc4 & ~bc2); + s[21] = bc1 ^ (bc5 & ~bc3); + s[2] = bc2 ^ (bc6 & ~bc4); + s[3] = bc3 ^ (bc7 & ~bc5); + s[34] = bc4 ^ (bc8 & ~bc6); + s[35] = bc5 ^ (bc9 & ~bc7); + s[16] = bc6 ^ (bc0 & ~bc8); + s[17] = bc7 ^ (bc1 & ~bc9); + s[48] = bc8 ^ (bc2 & ~bc0); + s[49] = bc9 ^ (bc3 & ~bc1); + + t0 = s[10] ^ d0; + t1 = s[11] ^ d1; + bc6 = (t1 << 9) | (t0 >>> 23); + bc7 = (t0 << 9) | (t1 >>> 23); + t0 = s[42] ^ d2; + t1 = s[43] ^ d3; + bc8 = (t0 << 2) | (t1 >>> 30); + bc9 = (t1 << 2) | (t0 >>> 30); + t0 = s[24] ^ d4; + t1 = s[25] ^ d5; + bc0 = (t1 << 30) | (t0 >>> 2); + bc1 = (t0 << 30) | (t1 >>> 2); + t0 = s[6] ^ d6; + t1 = s[7] ^ d7; + bc2 = (t1 << 23) | (t0 >>> 9); + bc3 = (t0 << 23) | (t1 >>> 9); + t0 = s[38] ^ d8; + t1 = s[39] ^ d9; + bc4 = (t1 << 7) | (t0 >>> 25); + bc5 = (t0 << 7) | (t1 >>> 25); + s[10] = bc0 ^ (bc4 & ~bc2); + s[11] = bc1 ^ (bc5 & ~bc3); + s[42] = bc2 ^ (bc6 & ~bc4); + s[43] = bc3 ^ (bc7 & ~bc5); + s[24] = bc4 ^ (bc8 & ~bc6); + s[25] = bc5 ^ (bc9 & ~bc7); + s[6] = bc6 ^ (bc0 & ~bc8); + s[7] = bc7 ^ (bc1 & ~bc9); + s[38] = bc8 ^ (bc2 & ~bc0); + s[39] = bc9 ^ (bc3 & ~bc1); + + // Round 3 + bc0 = s[0] ^ s[10] ^ s[20] ^ s[30] ^ s[40]; + bc1 = s[1] ^ s[11] ^ s[21] ^ s[31] ^ s[41]; + bc2 = s[2] ^ s[12] ^ s[22] ^ s[32] ^ s[42]; + bc3 = s[3] ^ s[13] ^ s[23] ^ s[33] ^ s[43]; + bc4 = s[4] ^ s[14] ^ s[24] ^ s[34] ^ s[44]; + bc5 = s[5] ^ s[15] ^ s[25] ^ s[35] ^ s[45]; + bc6 = s[6] ^ s[16] ^ s[26] ^ s[36] ^ s[46]; + bc7 = s[7] ^ s[17] ^ s[27] ^ s[37] ^ s[47]; + bc8 = s[8] ^ s[18] ^ s[28] ^ s[38] ^ s[48]; + bc9 = s[9] ^ s[19] ^ s[29] ^ s[39] ^ s[49]; + + d0 = bc8 ^ ((bc2 << 1) | (bc3 >>> 31)); + d1 = bc9 ^ ((bc3 << 1) | (bc2 >>> 31)); + d2 = bc0 ^ ((bc4 << 1) | (bc5 >>> 31)); + d3 = bc1 ^ ((bc5 << 1) | (bc4 >>> 31)); + d4 = bc2 ^ ((bc6 << 1) | (bc7 >>> 31)); + d5 = bc3 ^ ((bc7 << 1) | (bc6 >>> 31)); + d6 = bc4 ^ ((bc8 << 1) | (bc9 >>> 31)); + d7 = bc5 ^ ((bc9 << 1) | (bc8 >>> 31)); + d8 = bc6 ^ ((bc0 << 1) | (bc1 >>> 31)); + d9 = bc7 ^ ((bc1 << 1) | (bc0 >>> 31)); + + bc0 = s[0] ^ d0; + bc1 = s[1] ^ d1; + t0 = s[22] ^ d2; + t1 = s[23] ^ d3; + bc2 = (t1 << 12) | (t0 >>> 20); + bc3 = (t0 << 12) | (t1 >>> 20); + t0 = s[44] ^ d4; + t1 = s[45] ^ d5; + bc4 = (t1 << 11) | (t0 >>> 21); + bc5 = (t0 << 11) | (t1 >>> 21); + t0 = s[16] ^ d6; + t1 = s[17] ^ d7; + bc6 = (t0 << 21) | (t1 >>> 11); + bc7 = (t1 << 21) | (t0 >>> 11); + t0 = s[38] ^ d8; + t1 = s[39] ^ d9; + bc8 = (t0 << 14) | (t1 >>> 18); + bc9 = (t1 << 14) | (t0 >>> 18); + s[0] = bc0 ^ (bc4 & ~bc2) ^ KECCAK_RC[n + 4]; + s[1] = bc1 ^ (bc5 & ~bc3) ^ KECCAK_RC[n + 5]; + s[22] = bc2 ^ (bc6 & ~bc4); + s[23] = bc3 ^ (bc7 & ~bc5); + s[44] = bc4 ^ (bc8 & ~bc6); + s[45] = bc5 ^ (bc9 & ~bc7); + s[16] = bc6 ^ (bc0 & ~bc8); + s[17] = bc7 ^ (bc1 & ~bc9); + s[38] = bc8 ^ (bc2 & ~bc0); + s[39] = bc9 ^ (bc3 & ~bc1); + + t0 = s[30] ^ d0; + t1 = s[31] ^ d1; + bc4 = (t0 << 3) | (t1 >>> 29); + bc5 = (t1 << 3) | (t0 >>> 29); + t0 = s[2] ^ d2; + t1 = s[3] ^ d3; + bc6 = (t1 << 13) | (t0 >>> 19); + bc7 = (t0 << 13) | (t1 >>> 19); + t0 = s[24] ^ d4; + t1 = s[25] ^ d5; + bc8 = (t1 << 29) | (t0 >>> 3); + bc9 = (t0 << 29) | (t1 >>> 3); + t0 = s[46] ^ d6; + t1 = s[47] ^ d7; + bc0 = (t0 << 28) | (t1 >>> 4); + bc1 = (t1 << 28) | (t0 >>> 4); + t0 = s[18] ^ d8; + t1 = s[19] ^ d9; + bc2 = (t0 << 20) | (t1 >>> 12); + bc3 = (t1 << 20) | (t0 >>> 12); + s[30] = bc0 ^ (bc4 & ~bc2); + s[31] = bc1 ^ (bc5 & ~bc3); + s[2] = bc2 ^ (bc6 & ~bc4); + s[3] = bc3 ^ (bc7 & ~bc5); + s[24] = bc4 ^ (bc8 & ~bc6); + s[25] = bc5 ^ (bc9 & ~bc7); + s[46] = bc6 ^ (bc0 & ~bc8); + s[47] = bc7 ^ (bc1 & ~bc9); + s[18] = bc8 ^ (bc2 & ~bc0); + s[19] = bc9 ^ (bc3 & ~bc1); + + t0 = s[10] ^ d0; + t1 = s[11] ^ d1; + bc8 = (t0 << 18) | (t1 >>> 14); + bc9 = (t1 << 18) | (t0 >>> 14); + t0 = s[32] ^ d2; + t1 = s[33] ^ d3; + bc0 = (t0 << 1) | (t1 >>> 31); + bc1 = (t1 << 1) | (t0 >>> 31); + t0 = s[4] ^ d4; + t1 = s[5] ^ d5; + bc2 = (t0 << 6) | (t1 >>> 26); + bc3 = (t1 << 6) | (t0 >>> 26); + t0 = s[26] ^ d6; + t1 = s[27] ^ d7; + bc4 = (t0 << 25) | (t1 >>> 7); + bc5 = (t1 << 25) | (t0 >>> 7); + t0 = s[48] ^ d8; + t1 = s[49] ^ d9; + bc6 = (t0 << 8) | (t1 >>> 24); + bc7 = (t1 << 8) | (t0 >>> 24); + s[10] = bc0 ^ (bc4 & ~bc2); + s[11] = bc1 ^ (bc5 & ~bc3); + s[32] = bc2 ^ (bc6 & ~bc4); + s[33] = bc3 ^ (bc7 & ~bc5); + s[4] = bc4 ^ (bc8 & ~bc6); + s[5] = bc5 ^ (bc9 & ~bc7); + s[26] = bc6 ^ (bc0 & ~bc8); + s[27] = bc7 ^ (bc1 & ~bc9); + s[48] = bc8 ^ (bc2 & ~bc0); + s[49] = bc9 ^ (bc3 & ~bc1); + + t0 = s[40] ^ d0; + t1 = s[41] ^ d1; + bc2 = (t1 << 4) | (t0 >>> 28); + bc3 = (t0 << 4) | (t1 >>> 28); + t0 = s[12] ^ d2; + t1 = s[13] ^ d3; + bc4 = (t0 << 10) | (t1 >>> 22); + bc5 = (t1 << 10) | (t0 >>> 22); + t0 = s[34] ^ d4; + t1 = s[35] ^ d5; + bc6 = (t0 << 15) | (t1 >>> 17); + bc7 = (t1 << 15) | (t0 >>> 17); + t0 = s[6] ^ d6; + t1 = s[7] ^ d7; + bc8 = (t1 << 24) | (t0 >>> 8); + bc9 = (t0 << 24) | (t1 >>> 8); + t0 = s[28] ^ d8; + t1 = s[29] ^ d9; + bc0 = (t0 << 27) | (t1 >>> 5); + bc1 = (t1 << 27) | (t0 >>> 5); + s[40] = bc0 ^ (bc4 & ~bc2); + s[41] = bc1 ^ (bc5 & ~bc3); + s[12] = bc2 ^ (bc6 & ~bc4); + s[13] = bc3 ^ (bc7 & ~bc5); + s[34] = bc4 ^ (bc8 & ~bc6); + s[35] = bc5 ^ (bc9 & ~bc7); + s[6] = bc6 ^ (bc0 & ~bc8); + s[7] = bc7 ^ (bc1 & ~bc9); + s[28] = bc8 ^ (bc2 & ~bc0); + s[29] = bc9 ^ (bc3 & ~bc1); + + t0 = s[20] ^ d0; + t1 = s[21] ^ d1; + bc6 = (t1 << 9) | (t0 >>> 23); + bc7 = (t0 << 9) | (t1 >>> 23); + t0 = s[42] ^ d2; + t1 = s[43] ^ d3; + bc8 = (t0 << 2) | (t1 >>> 30); + bc9 = (t1 << 2) | (t0 >>> 30); + t0 = s[14] ^ d4; + t1 = s[15] ^ d5; + bc0 = (t1 << 30) | (t0 >>> 2); + bc1 = (t0 << 30) | (t1 >>> 2); + t0 = s[36] ^ d6; + t1 = s[37] ^ d7; + bc2 = (t1 << 23) | (t0 >>> 9); + bc3 = (t0 << 23) | (t1 >>> 9); + t0 = s[8] ^ d8; + t1 = s[9] ^ d9; + bc4 = (t1 << 7) | (t0 >>> 25); + bc5 = (t0 << 7) | (t1 >>> 25); + s[20] = bc0 ^ (bc4 & ~bc2); + s[21] = bc1 ^ (bc5 & ~bc3); + s[42] = bc2 ^ (bc6 & ~bc4); + s[43] = bc3 ^ (bc7 & ~bc5); + s[14] = bc4 ^ (bc8 & ~bc6); + s[15] = bc5 ^ (bc9 & ~bc7); + s[36] = bc6 ^ (bc0 & ~bc8); + s[37] = bc7 ^ (bc1 & ~bc9); + s[8] = bc8 ^ (bc2 & ~bc0); + s[9] = bc9 ^ (bc3 & ~bc1); + + // Round 4 + bc0 = s[0] ^ s[10] ^ s[20] ^ s[30] ^ s[40]; + bc1 = s[1] ^ s[11] ^ s[21] ^ s[31] ^ s[41]; + bc2 = s[2] ^ s[12] ^ s[22] ^ s[32] ^ s[42]; + bc3 = s[3] ^ s[13] ^ s[23] ^ s[33] ^ s[43]; + bc4 = s[4] ^ s[14] ^ s[24] ^ s[34] ^ s[44]; + bc5 = s[5] ^ s[15] ^ s[25] ^ s[35] ^ s[45]; + bc6 = s[6] ^ s[16] ^ s[26] ^ s[36] ^ s[46]; + bc7 = s[7] ^ s[17] ^ s[27] ^ s[37] ^ s[47]; + bc8 = s[8] ^ s[18] ^ s[28] ^ s[38] ^ s[48]; + bc9 = s[9] ^ s[19] ^ s[29] ^ s[39] ^ s[49]; + + d0 = bc8 ^ ((bc2 << 1) | (bc3 >>> 31)); + d1 = bc9 ^ ((bc3 << 1) | (bc2 >>> 31)); + d2 = bc0 ^ ((bc4 << 1) | (bc5 >>> 31)); + d3 = bc1 ^ ((bc5 << 1) | (bc4 >>> 31)); + d4 = bc2 ^ ((bc6 << 1) | (bc7 >>> 31)); + d5 = bc3 ^ ((bc7 << 1) | (bc6 >>> 31)); + d6 = bc4 ^ ((bc8 << 1) | (bc9 >>> 31)); + d7 = bc5 ^ ((bc9 << 1) | (bc8 >>> 31)); + d8 = bc6 ^ ((bc0 << 1) | (bc1 >>> 31)); + d9 = bc7 ^ ((bc1 << 1) | (bc0 >>> 31)); + + bc0 = s[0] ^ d0; + bc1 = s[1] ^ d1; + t0 = s[2] ^ d2; + t1 = s[3] ^ d3; + bc2 = (t1 << 12) | (t0 >>> 20); + bc3 = (t0 << 12) | (t1 >>> 20); + t0 = s[4] ^ d4; + t1 = s[5] ^ d5; + bc4 = (t1 << 11) | (t0 >>> 21); + bc5 = (t0 << 11) | (t1 >>> 21); + t0 = s[6] ^ d6; + t1 = s[7] ^ d7; + bc6 = (t0 << 21) | (t1 >>> 11); + bc7 = (t1 << 21) | (t0 >>> 11); + t0 = s[8] ^ d8; + t1 = s[9] ^ d9; + bc8 = (t0 << 14) | (t1 >>> 18); + bc9 = (t1 << 14) | (t0 >>> 18); + s[0] = bc0 ^ (bc4 & ~bc2) ^ KECCAK_RC[n + 6]; + s[1] = bc1 ^ (bc5 & ~bc3) ^ KECCAK_RC[n + 7]; + s[2] = bc2 ^ (bc6 & ~bc4); + s[3] = bc3 ^ (bc7 & ~bc5); + s[4] = bc4 ^ (bc8 & ~bc6); + s[5] = bc5 ^ (bc9 & ~bc7); + s[6] = bc6 ^ (bc0 & ~bc8); + s[7] = bc7 ^ (bc1 & ~bc9); + s[8] = bc8 ^ (bc2 & ~bc0); + s[9] = bc9 ^ (bc3 & ~bc1); + + t0 = s[10] ^ d0; + t1 = s[11] ^ d1; + bc4 = (t0 << 3) | (t1 >>> 29); + bc5 = (t1 << 3) | (t0 >>> 29); + t0 = s[12] ^ d2; + t1 = s[13] ^ d3; + bc6 = (t1 << 13) | (t0 >>> 19); + bc7 = (t0 << 13) | (t1 >>> 19); + t0 = s[14] ^ d4; + t1 = s[15] ^ d5; + bc8 = (t1 << 29) | (t0 >>> 3); + bc9 = (t0 << 29) | (t1 >>> 3); + t0 = s[16] ^ d6; + t1 = s[17] ^ d7; + bc0 = (t0 << 28) | (t1 >>> 4); + bc1 = (t1 << 28) | (t0 >>> 4); + t0 = s[18] ^ d8; + t1 = s[19] ^ d9; + bc2 = (t0 << 20) | (t1 >>> 12); + bc3 = (t1 << 20) | (t0 >>> 12); + s[10] = bc0 ^ (bc4 & ~bc2); + s[11] = bc1 ^ (bc5 & ~bc3); + s[12] = bc2 ^ (bc6 & ~bc4); + s[13] = bc3 ^ (bc7 & ~bc5); + s[14] = bc4 ^ (bc8 & ~bc6); + s[15] = bc5 ^ (bc9 & ~bc7); + s[16] = bc6 ^ (bc0 & ~bc8); + s[17] = bc7 ^ (bc1 & ~bc9); + s[18] = bc8 ^ (bc2 & ~bc0); + s[19] = bc9 ^ (bc3 & ~bc1); + + t0 = s[20] ^ d0; + t1 = s[21] ^ d1; + bc8 = (t0 << 18) | (t1 >>> 14); + bc9 = (t1 << 18) | (t0 >>> 14); + t0 = s[22] ^ d2; + t1 = s[23] ^ d3; + bc0 = (t0 << 1) | (t1 >>> 31); + bc1 = (t1 << 1) | (t0 >>> 31); + t0 = s[24] ^ d4; + t1 = s[25] ^ d5; + bc2 = (t0 << 6) | (t1 >>> 26); + bc3 = (t1 << 6) | (t0 >>> 26); + t0 = s[26] ^ d6; + t1 = s[27] ^ d7; + bc4 = (t0 << 25) | (t1 >>> 7); + bc5 = (t1 << 25) | (t0 >>> 7); + t0 = s[28] ^ d8; + t1 = s[29] ^ d9; + bc6 = (t0 << 8) | (t1 >>> 24); + bc7 = (t1 << 8) | (t0 >>> 24); + s[20] = bc0 ^ (bc4 & ~bc2); + s[21] = bc1 ^ (bc5 & ~bc3); + s[22] = bc2 ^ (bc6 & ~bc4); + s[23] = bc3 ^ (bc7 & ~bc5); + s[24] = bc4 ^ (bc8 & ~bc6); + s[25] = bc5 ^ (bc9 & ~bc7); + s[26] = bc6 ^ (bc0 & ~bc8); + s[27] = bc7 ^ (bc1 & ~bc9); + s[28] = bc8 ^ (bc2 & ~bc0); + s[29] = bc9 ^ (bc3 & ~bc1); + + t0 = s[30] ^ d0; + t1 = s[31] ^ d1; + bc2 = (t1 << 4) | (t0 >>> 28); + bc3 = (t0 << 4) | (t1 >>> 28); + t0 = s[32] ^ d2; + t1 = s[33] ^ d3; + bc4 = (t0 << 10) | (t1 >>> 22); + bc5 = (t1 << 10) | (t0 >>> 22); + t0 = s[34] ^ d4; + t1 = s[35] ^ d5; + bc6 = (t0 << 15) | (t1 >>> 17); + bc7 = (t1 << 15) | (t0 >>> 17); + t0 = s[36] ^ d6; + t1 = s[37] ^ d7; + bc8 = (t1 << 24) | (t0 >>> 8); + bc9 = (t0 << 24) | (t1 >>> 8); + t0 = s[38] ^ d8; + t1 = s[39] ^ d9; + bc0 = (t0 << 27) | (t1 >>> 5); + bc1 = (t1 << 27) | (t0 >>> 5); + s[30] = bc0 ^ (bc4 & ~bc2); + s[31] = bc1 ^ (bc5 & ~bc3); + s[32] = bc2 ^ (bc6 & ~bc4); + s[33] = bc3 ^ (bc7 & ~bc5); + s[34] = bc4 ^ (bc8 & ~bc6); + s[35] = bc5 ^ (bc9 & ~bc7); + s[36] = bc6 ^ (bc0 & ~bc8); + s[37] = bc7 ^ (bc1 & ~bc9); + s[38] = bc8 ^ (bc2 & ~bc0); + s[39] = bc9 ^ (bc3 & ~bc1); + + t0 = s[40] ^ d0; + t1 = s[41] ^ d1; + bc6 = (t1 << 9) | (t0 >>> 23); + bc7 = (t0 << 9) | (t1 >>> 23); + t0 = s[42] ^ d2; + t1 = s[43] ^ d3; + bc8 = (t0 << 2) | (t1 >>> 30); + bc9 = (t1 << 2) | (t0 >>> 30); + t0 = s[44] ^ d4; + t1 = s[45] ^ d5; + bc0 = (t1 << 30) | (t0 >>> 2); + bc1 = (t0 << 30) | (t1 >>> 2); + t0 = s[46] ^ d6; + t1 = s[47] ^ d7; + bc2 = (t1 << 23) | (t0 >>> 9); + bc3 = (t0 << 23) | (t1 >>> 9); + t0 = s[48] ^ d8; + t1 = s[49] ^ d9; + bc4 = (t1 << 7) | (t0 >>> 25); + bc5 = (t0 << 7) | (t1 >>> 25); + s[40] = bc0 ^ (bc4 & ~bc2); + s[41] = bc1 ^ (bc5 & ~bc3); + s[42] = bc2 ^ (bc6 & ~bc4); + s[43] = bc3 ^ (bc7 & ~bc5); + s[44] = bc4 ^ (bc8 & ~bc6); + s[45] = bc5 ^ (bc9 & ~bc7); + s[46] = bc6 ^ (bc0 & ~bc8); + s[47] = bc7 ^ (bc1 & ~bc9); + s[48] = bc8 ^ (bc2 & ~bc0); + s[49] = bc9 ^ (bc3 & ~bc1); + } +} diff --git a/std/hash/_sha3/sha3.ts b/std/hash/_sha3/sha3.ts new file mode 100644 index 00000000000000..393889210bf0e9 --- /dev/null +++ b/std/hash/_sha3/sha3.ts @@ -0,0 +1,54 @@ +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. + +import { Sponge } from "./sponge.ts"; +import { keccakf } from "./keccakf.ts"; + +/* eslint-disable @typescript-eslint/camelcase, @typescript-eslint/class-name-casing */ + +/** Sha3-224 hash */ +export class Sha3_224 extends Sponge { + constructor() { + super({ + bitsize: 224, + rate: 144, + dsbyte: 6, + permutator: keccakf, + }); + } +} + +/** Sha3-256 hash */ +export class Sha3_256 extends Sponge { + constructor() { + super({ + bitsize: 256, + rate: 136, + dsbyte: 6, + permutator: keccakf, + }); + } +} + +/** Sha3-384 hash */ +export class Sha3_384 extends Sponge { + constructor() { + super({ + bitsize: 384, + rate: 104, + dsbyte: 6, + permutator: keccakf, + }); + } +} + +/** Sha3-512 hash */ +export class Sha3_512 extends Sponge { + constructor() { + super({ + bitsize: 512, + rate: 72, + dsbyte: 6, + permutator: keccakf, + }); + } +} diff --git a/std/hash/_sha3/shake.ts b/std/hash/_sha3/shake.ts new file mode 100644 index 00000000000000..05c699ea5ac1e0 --- /dev/null +++ b/std/hash/_sha3/shake.ts @@ -0,0 +1,51 @@ +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. + +import { Sponge } from "./sponge.ts"; +import { keccakf } from "./keccakf.ts"; + +/** Shake128 hash */ +export class Shake128 extends Sponge { + /** + * Instantiates a new Shake128 hash + * @param bitsize length of hash in bits + */ + constructor(bitsize: number) { + if (bitsize < 8) { + throw new Error("shake128: `bitsize` too small"); + } + + if (bitsize % 8 !== 0) { + throw new Error("shake128: `bitsize` must be multiple of 8"); + } + + super({ + bitsize: bitsize, + rate: 168, + dsbyte: 0x1f, + permutator: keccakf, + }); + } +} + +/** + * Instantiates a new Shake256 hash + * @param bitsize length of hash in bits + */ +export class Shake256 extends Sponge { + constructor(bitsize: number) { + if (bitsize < 8) { + throw new Error("shake256: `bitsize` too small"); + } + + if (bitsize % 8 !== 0) { + throw new Error("shake256: `bitsize` must be multiple of 8"); + } + + super({ + bitsize: bitsize, + rate: 136, + dsbyte: 0x1f, + permutator: keccakf, + }); + } +} diff --git a/std/hash/_sha3/sponge.ts b/std/hash/_sha3/sponge.ts new file mode 100644 index 00000000000000..a5705e13e231f0 --- /dev/null +++ b/std/hash/_sha3/sponge.ts @@ -0,0 +1,111 @@ +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. + +import * as hex from "../../encoding/hex.ts"; + +type SpongePermutator = (data: Uint8Array) => void; + +/** Sponge construction option */ +export interface SpongeOption { + bitsize: number; + rate: number; + dsbyte: number; + permutator: SpongePermutator; +} + +export type Message = string | ArrayBuffer; + +const STATE_SIZE = 200; +const TYPE_ERROR_MSG = "sha3: `data` is invalid type"; + +/** Sponge construction */ +export class Sponge { + #option: SpongeOption; + #state: Uint8Array; + #rp: number; + #absorbing: boolean; + + constructor(option: SpongeOption) { + this.#option = option; + this.#state = new Uint8Array(STATE_SIZE); + this.#rp = 0; + this.#absorbing = true; + } + + /** Applies padding to internal state */ + private pad(): void { + this.#state[this.#rp] ^= this.#option.dsbyte; + this.#state[this.#option.rate - 1] ^= 0x80; + } + + /** Squeezes internal state */ + protected squeeze(length: number): Uint8Array { + if (length < 0) { + throw new Error("sha3: length cannot be negative"); + } + + this.pad(); + + const hash = new Uint8Array(length); + let pos = 0; + while (length > 0) { + const r = length > this.#option.rate ? this.#option.rate : length; + this.#option.permutator(this.#state); + hash.set(this.#state.slice(0, r), pos); + length -= r; + pos += r; + } + + this.#absorbing = false; + return hash; + } + + /** Updates internal state by absorbing */ + update(data: Message): this { + if (!this.#absorbing) { + throw new Error("sha3: cannot update already finalized hash"); + } + + let msg: Uint8Array; + + if (typeof data === "string") { + msg = new TextEncoder().encode(data as string); + } else if (typeof data === "object") { + if (data instanceof ArrayBuffer || ArrayBuffer.isView(data)) { + msg = new Uint8Array(data); + } else { + throw new Error(TYPE_ERROR_MSG); + } + } else { + throw new Error(TYPE_ERROR_MSG); + } + + let rp = this.#rp; + + for (let i = 0; i < msg.length; ++i) { + this.#state[rp++] ^= msg[i]; + if (rp >= this.#option.rate) { + this.#option.permutator(this.#state); + rp = 0; + } + } + + this.#rp = rp; + return this; + } + + /** Returns the hash in ArrayBuffer */ + digest(): ArrayBuffer { + return this.squeeze(this.#option.bitsize >> 3); + } + + /** Returns the hash in given format */ + toString(format: "hex" = "hex"): string { + const rawOutput = this.squeeze(this.#option.bitsize >> 3); + switch (format) { + case "hex": + return hex.encodeToString(rawOutput); + default: + throw new Error("sha3: invalid output format"); + } + } +} diff --git a/std/hash/sha1_test.ts b/std/hash/sha1_test.ts index 0387c914ae8de6..36702d55afb700 100644 --- a/std/hash/sha1_test.ts +++ b/std/hash/sha1_test.ts @@ -18,7 +18,7 @@ function toHexString(value: number[] | ArrayBuffer): string { } // prettier-ignore -// dprint-ignore +// deno-fmt-ignore const fixtures: { sha1: Record>; } = { diff --git a/std/hash/sha256.ts b/std/hash/sha256.ts index c5635cacd4ac4c..61da5a578ded26 100644 --- a/std/hash/sha256.ts +++ b/std/hash/sha256.ts @@ -15,7 +15,7 @@ const HEX_CHARS = "0123456789abcdef".split(""); const EXTRA = [-2147483648, 8388608, 32768, 128] as const; const SHIFT = [24, 16, 8, 0] as const; // prettier-ignore -// dprint-ignore +// deno-fmt-ignore const K = [ 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, diff --git a/std/hash/sha256_test.ts b/std/hash/sha256_test.ts index 92c7e3d5b334b2..68c67b5ee24d3b 100644 --- a/std/hash/sha256_test.ts +++ b/std/hash/sha256_test.ts @@ -19,7 +19,7 @@ function toHexString(value: number[] | ArrayBuffer): string { } // prettier-ignore -// dprint-ignore +// deno-fmt-ignore const fixtures: { sha256: Record>; sha224: Record>; @@ -158,35 +158,35 @@ const fixtures: { }; // prettier-ignore -// dprint-ignore +// deno-fmt-ignore fixtures.sha256.Uint8Array = { '182889f925ae4e5cc37118ded6ed87f7bdc7cab5ec5e78faef2e50048999473f': new Uint8Array([211, 212]), 'd7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592': new Uint8Array([84, 104, 101, 32, 113, 117, 105, 99, 107, 32, 98, 114, 111, 119, 110, 32, 102, 111, 120, 32, 106, 117, 109, 112, 115, 32, 111, 118, 101, 114, 32, 116, 104, 101, 32, 108, 97, 122, 121, 32, 100, 111, 103]) }; // prettier-ignore -// dprint-ignore +// deno-fmt-ignore fixtures.sha256.Int8Array = { 'd7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592': new Int8Array([84, 104, 101, 32, 113, 117, 105, 99, 107, 32, 98, 114, 111, 119, 110, 32, 102, 111, 120, 32, 106, 117, 109, 112, 115, 32, 111, 118, 101, 114, 32, 116, 104, 101, 32, 108, 97, 122, 121, 32, 100, 111, 103]) }; // prettier-ignore -// dprint-ignore +// deno-fmt-ignore fixtures.sha256.ArrayBuffer = { 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855': new ArrayBuffer(0), '6e340b9cffb37a989ca544e6bb780a2c78901d3fb33738768511a30617afa01d': new ArrayBuffer(1) }; // prettier-ignore -// dprint-ignore +// deno-fmt-ignore fixtures.sha224.Uint8Array = { 'e17541396a3ecd1cd5a2b968b84e597e8eae3b0ea3127963bf48dd3b': new Uint8Array([211, 212]), '730e109bd7a8a32b1cb9d9a09aa2325d2430587ddbc0c38bad911525': new Uint8Array([84, 104, 101, 32, 113, 117, 105, 99, 107, 32, 98, 114, 111, 119, 110, 32, 102, 111, 120, 32, 106, 117, 109, 112, 115, 32, 111, 118, 101, 114, 32, 116, 104, 101, 32, 108, 97, 122, 121, 32, 100, 111, 103]) }; // prettier-ignore -// dprint-ignore +// deno-fmt-ignore fixtures.sha224.Int8Array = { '730e109bd7a8a32b1cb9d9a09aa2325d2430587ddbc0c38bad911525': new Int8Array([84, 104, 101, 32, 113, 117, 105, 99, 107, 32, 98, 114, 111, 119, 110, 32, 102, 111, 120, 32, 106, 117, 109, 112, 115, 32, 111, 118, 101, 114, 32, 116, 104, 101, 32, 108, 97, 122, 121, 32, 100, 111, 103]) }; // prettier-ignore -// dprint-ignore +// deno-fmt-ignore fixtures.sha224.ArrayBuffer = { 'd14a028c2a3a2bc9476102bb288234c415a2b01f828ea62ac5b3e42f': new ArrayBuffer(0), 'fff9292b4201617bdc4d3053fce02734166a683d7d858a7f5f59b073': new ArrayBuffer(1), diff --git a/std/hash/sha3.ts b/std/hash/sha3.ts new file mode 100644 index 00000000000000..b5154fbdff63f6 --- /dev/null +++ b/std/hash/sha3.ts @@ -0,0 +1,6 @@ +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. + +/* eslint-disable-next-line @typescript-eslint/camelcase */ +export { Sha3_224, Sha3_256, Sha3_384, Sha3_512 } from "./_sha3/sha3.ts"; +export { Keccak224, Keccak256, Keccak384, Keccak512 } from "./_sha3/keccak.ts"; +export { Shake128, Shake256 } from "./_sha3/shake.ts"; diff --git a/std/hash/sha3_test.ts b/std/hash/sha3_test.ts new file mode 100644 index 00000000000000..6812f6209e775a --- /dev/null +++ b/std/hash/sha3_test.ts @@ -0,0 +1,575 @@ +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. + +/* eslint-disable @typescript-eslint/camelcase */ + +const { test } = Deno; +import { assertEquals, assertThrows } from "../testing/asserts.ts"; +import { + Keccak224, + Keccak256, + Keccak384, + Keccak512, + Sha3_224, + Sha3_256, + Sha3_384, + Sha3_512, + Shake128, + Shake256, +} from "./sha3.ts"; +import * as hex from "../encoding/hex.ts"; + +const millionAs = "a".repeat(1000000); + +const testSetSha3_224 = [ + ["", "6b4e03423667dbb73b6e15454f0eb1abd4597f9a1b078e3f5b5a6bc7"], + ["abc", "e642824c3f8cf24ad09234ee7d3c766fc9a3a5168d0c94ad73b46fdf"], + [ + "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", + "8a24108b154ada21c9fd5574494479ba5c7e7ab76ef264ead0fcce33", + ], + [ + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "f9019111996dcf160e284e320fd6d8825cabcd41a5ffdc4c5e9d64b6", + ], + [millionAs, "d69335b93325192e516a912e6d19a15cb51c6ed5c15243e7a7fd653c"], +]; + +const testSetSha3_256 = [ + ["", "a7ffc6f8bf1ed76651c14756a061d662f580ff4de43b49fa82d80a4b80f8434a"], + ["abc", "3a985da74fe225b2045c172d6bd390bd855f086e3e9d525b46bfe24511431532"], + [ + "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", + "41c0dba2a9d6240849100376a8235e2c82e1b9998a999e21db32dd97496d3376", + ], + [ + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "3fc5559f14db8e453a0a3091edbd2bc25e11528d81c66fa570a4efdcc2695ee1", + ], + [ + millionAs, + "5c8875ae474a3634ba4fd55ec85bffd661f32aca75c6d699d0cdcb6c115891c1", + ], +]; + +const testSetSha3_384 = [ + [ + "", + "0c63a75b845e4f7d01107d852e4c2485c51a50aaaa94fc61995e71bbee983a2ac3713831264adb47fb6bd1e058d5f004", + ], + [ + "abc", + "ec01498288516fc926459f58e2c6ad8df9b473cb0fc08c2596da7cf0e49be4b298d88cea927ac7f539f1edf228376d25", + ], + [ + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "3a4f3b6284e571238884e95655e8c8a60e068e4059a9734abc08823a900d161592860243f00619ae699a29092ed91a16", + ], + [ + "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", + "991c665755eb3a4b6bbdfb75c78a492e8c56a22c5c4d7e429bfdbc32b9d4ad5aa04a1f076e62fea19eef51acd0657c22", + ], + [ + millionAs, + "eee9e24d78c1855337983451df97c8ad9eedf256c6334f8e948d252d5e0e76847aa0774ddb90a842190d2c558b4b8340", + ], +]; + +const testSetSha3_512 = [ + [ + "", + "a69f73cca23a9ac5c8b567dc185a756e97c982164fe25859e0d1dcc1475c80a615b2123af1f5f94c11e3e9402c3ac558f500199d95b6d3e301758586281dcd26", + ], + [ + "abc", + "b751850b1a57168a5693cd924b6b096e08f621827444f70d884f5d0240d2712e10e116e9192af3c91a7ec57647e3934057340b4cf408d5a56592f8274eec53f0", + ], + [ + "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", + "04a371e84ecfb5b8b77cb48610fca8182dd457ce6f326a0fd3d7ec2f1e91636dee691fbe0c985302ba1b0d8dc78c086346b533b49c030d99a27daf1139d6e75e", + ], + [ + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "a8ae722a78e10cbbc413886c02eb5b369a03f6560084aff566bd597bb7ad8c1ccd86e81296852359bf2faddb5153c0a7445722987875e74287adac21adebe952", + ], + [ + millionAs, + "3c3a876da14034ab60627c077bb98f7e120a2a5370212dffb3385a18d4f38859ed311d0a9d5141ce9cc5c66ee689b266a8aa18ace8282a0e0db596c90b0a7b87", + ], +]; + +const testSetKeccak224 = [ + ["", "f71837502ba8e10837bdd8d365adb85591895602fc552b48b7390abd"], + ["abc", "c30411768506ebe1c2871b1ee2e87d38df342317300a9b97a95ec6a8"], + [ + "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", + "e51faa2b4655150b931ee8d700dc202f763ca5f962c529eae55012b6", + ], + [millionAs, "19f9167be2a04c43abd0ed554788101b9c339031acc8e1468531303f"], +]; + +const testSetKeccak256 = [ + ["", "c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"], + ["abc", "4e03657aea45a94fc7d47ba826c8d667c0d1e6e33a64a036ec44f58fa12d6c45"], + [ + "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", + "45d3b367a6904e6e8d502ee04999a7c27647f91fa845d456525fd352ae3d7371", + ], + [ + millionAs, + "fadae6b49f129bbb812be8407b7b2894f34aecf6dbd1f9b0f0c7e9853098fc96", + ], +]; + +const testSetKeccak384 = [ + [ + "", + "2c23146a63a29acf99e73b88f8c24eaa7dc60aa771780ccc006afbfa8fe2479b2dd2b21362337441ac12b515911957ff", + ], + [ + "abc", + "f7df1165f033337be098e7d288ad6a2f74409d7a60b49c36642218de161b1f99f8c681e4afaf31a34db29fb763e3c28e", + ], + [ + "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", + "b41e8896428f1bcbb51e17abd6acc98052a3502e0d5bf7fa1af949b4d3c855e7c4dc2c390326b3f3e74c7b1e2b9a3657", + ], + [ + millionAs, + "0c8324e1ebc182822c5e2a086cac07c2fe00e3bce61d01ba8ad6b71780e2dec5fb89e5ae90cb593e57bc6258fdd94e17", + ], +]; + +const testSetKeccak512 = [ + [ + "", + "0eab42de4c3ceb9235fc91acffe746b29c29a8c366b7c60e4e67c466f36a4304c00fa9caf9d87976ba469bcbe06713b435f091ef2769fb160cdab33d3670680e", + ], + [ + "abc", + "18587dc2ea106b9a1563e32b3312421ca164c7f1f07bc922a9c83d77cea3a1e5d0c69910739025372dc14ac9642629379540c17e2a65b19d77aa511a9d00bb96", + ], + [ + "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", + "6aa6d3669597df6d5a007b00d09c20795b5c4218234e1698a944757a488ecdc09965435d97ca32c3cfed7201ff30e070cd947f1fc12b9d9214c467d342bcba5d", + ], + [ + millionAs, + "5cf53f2e556be5a624425ede23d0e8b2c7814b4ba0e4e09cbbf3c2fac7056f61e048fc341262875ebc58a5183fea651447124370c1ebf4d6c89bc9a7731063bb", + ], +]; + +const testSetShake128 = [ + ["", "7f9c2ba4e88f827d616045507605853e"], + ["abc", "5881092dd818bf5cf8a3ddb793fbcba7"], + [ + "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", + "1a96182b50fb8c7e74e0a707788f55e9", + ], + [millionAs, "9d222c79c4ff9d092cf6ca86143aa411"], +]; + +const testSetShake128_224 = [ + ["", "7f9c2ba4e88f827d616045507605853ed73b8093f6efbc88eb1a6eac"], + ["abc", "5881092dd818bf5cf8a3ddb793fbcba74097d5c526a6d35f97b83351"], + [ + "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", + "1a96182b50fb8c7e74e0a707788f55e98209b8d91fade8f32f8dd5cf", + ], + [millionAs, "9d222c79c4ff9d092cf6ca86143aa411e369973808ef97093255826c"], +]; + +const testSetShake128_2048 = [ + [ + "", + "7f9c2ba4e88f827d616045507605853ed73b8093f6efbc88eb1a6eacfa66ef263cb1eea988004b93103cfb0aeefd2a686e01fa4a58e8a3639ca8a1e3f9ae57e235b8cc873c23dc62b8d260169afa2f75ab916a58d974918835d25e6a435085b2badfd6dfaac359a5efbb7bcc4b59d538df9a04302e10c8bc1cbf1a0b3a5120ea17cda7cfad765f5623474d368ccca8af0007cd9f5e4c849f167a580b14aabdefaee7eef47cb0fca9767be1fda69419dfb927e9df07348b196691abaeb580b32def58538b8d23f87732ea63b02b4fa0f4873360e2841928cd60dd4cee8cc0d4c922a96188d032675c8ac850933c7aff1533b94c834adbb69c6115bad4692d8619", + ], + [ + "abc", + "5881092dd818bf5cf8a3ddb793fbcba74097d5c526a6d35f97b83351940f2cc844c50af32acd3f2cdd066568706f509bc1bdde58295dae3f891a9a0fca5783789a41f8611214ce612394df286a62d1a2252aa94db9c538956c717dc2bed4f232a0294c857c730aa16067ac1062f1201fb0d377cfb9cde4c63599b27f3462bba4a0ed296c801f9ff7f57302bb3076ee145f97a32ae68e76ab66c48d51675bd49acc29082f5647584e6aa01b3f5af057805f973ff8ecb8b226ac32ada6f01c1fcd4818cb006aa5b4cdb3611eb1e533c8964cacfdf31012cd3fb744d02225b988b475375faad996eb1b9176ecb0f8b2871723d6dbb804e23357e50732f5cfc904b1", + ], + [ + "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", + "1a96182b50fb8c7e74e0a707788f55e98209b8d91fade8f32f8dd5cff7bf21f54ee5f19550825a6e070030519e944263ac1c6765287065621f9fcb3201723e3223b63a46c2938aa953ba8401d0ea77b8d26490775566407b95673c0f4cc1ce9fd966148d7efdff26bbf9f48a21c6da35bfaa545654f70ae586ff10131420771483ec92edab408c767bf4c5b4fffaa80c8ca214d84c4dc700d0c50630b2ffc3793ea4d87258b4c9548c5485a5ca666ef73fbd816d418aea6395b503addd9b150f9e0663325f01e5518b71ffa1244ea284cebe0cea2f774d7b3a437dca3282e324777e19624bf2be3cd355c1bfbddb323a33f11efafb2448293501dc0454c6b72f", + ], + [ + millionAs, + "9d222c79c4ff9d092cf6ca86143aa411e369973808ef97093255826c5572ef58424c4b5c28475ffdcf981663867fec6321c1262e387bccf8ca676884c4a9d0c13bfa6869763d5ae4bbc9b3ccd09d1ca5ea7446538d69b3fb98c72b59a2b4817db5eadd9011f90fa71091931f8134f4f00b562e2fe105937270361c1909862ad45046e3932f5dd311ec72fec5f8fb8f60b45a3bee3f85bbf7fcedc6a555677648e0654b381941a86bd3e512657b0d57a7991fc4543f89d8290492222ce4a33e17602b3b99c009f7655f87535cdaa3716f58c47b8a157ad195f02809f27500b9254979311c6bb415968cd10431169a27d5a8d61e13a6b8b77af1f8b6dd2eefdea0", + ], +]; + +const testSetShake256 = [ + ["", "46b9dd2b0ba88d13233b3feb743eeb243fcd52ea62b81b82b50c27646ed5762f"], + ["abc", "483366601360a8771c6863080cc4114d8db44530f8f1e1ee4f94ea37e78b5739"], + [ + "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", + "4d8c2dd2435a0128eefbb8c36f6f87133a7911e18d979ee1ae6be5d4fd2e3329", + ], + [ + millionAs, + "3578a7a4ca9137569cdf76ed617d31bb994fca9c1bbf8b184013de8234dfd13a", + ], +]; + +const testSetShake256_128 = [ + ["", "46b9dd2b0ba88d13233b3feb743eeb24"], + ["abc", "483366601360a8771c6863080cc4114d"], + [ + "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", + "4d8c2dd2435a0128eefbb8c36f6f8713", + ], + [millionAs, "3578a7a4ca9137569cdf76ed617d31bb"], +]; + +const testSetShake256_384 = [ + [ + "", + "46b9dd2b0ba88d13233b3feb743eeb243fcd52ea62b81b82b50c27646ed5762fd75dc4ddd8c0f200cb05019d67b592f6", + ], + [ + "abc", + "483366601360a8771c6863080cc4114d8db44530f8f1e1ee4f94ea37e78b5739d5a15bef186a5386c75744c0527e1faa", + ], + [ + "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", + "4d8c2dd2435a0128eefbb8c36f6f87133a7911e18d979ee1ae6be5d4fd2e332940d8688a4e6a59aa8060f1f9bc996c05", + ], + [ + millionAs, + "3578a7a4ca9137569cdf76ed617d31bb994fca9c1bbf8b184013de8234dfd13a3fd124d4df76c0a539ee7dd2f6e1ec34", + ], +]; + +const testSetShake256_512 = [ + [ + "", + "46b9dd2b0ba88d13233b3feb743eeb243fcd52ea62b81b82b50c27646ed5762fd75dc4ddd8c0f200cb05019d67b592f6fc821c49479ab48640292eacb3b7c4be", + ], + [ + "abc", + "483366601360a8771c6863080cc4114d8db44530f8f1e1ee4f94ea37e78b5739d5a15bef186a5386c75744c0527e1faa9f8726e462a12a4feb06bd8801e751e4", + ], + [ + "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", + "4d8c2dd2435a0128eefbb8c36f6f87133a7911e18d979ee1ae6be5d4fd2e332940d8688a4e6a59aa8060f1f9bc996c05aca3c696a8b66279dc672c740bb224ec", + ], + [ + millionAs, + "3578a7a4ca9137569cdf76ed617d31bb994fca9c1bbf8b184013de8234dfd13a3fd124d4df76c0a539ee7dd2f6e1ec346124c815d9410e145eb561bcd97b18ab", + ], +]; + +function s2b(data: string): Uint8Array { + return new TextEncoder().encode(data); +} + +test("[hash/sha3] testSha3-224Raw", () => { + const sha3sum = (data: ArrayBuffer): ArrayBuffer => { + const sha3 = new Sha3_224(); + return sha3.update(data).digest(); + }; + + for (const [input, output] of testSetSha3_224) { + const rawOutput = hex.decodeString(output); + assertEquals(sha3sum(s2b(input)), rawOutput); + } +}); + +test("[hash/sha3] testSha3-224String", () => { + const sha3sum = (data: string): string => { + const sha3 = new Sha3_224(); + return sha3.update(data).toString(); + }; + + for (const [input, output] of testSetSha3_224) { + assertEquals(sha3sum(input), output); + } +}); + +test("[hash/sha3] testSha3-256Raw", () => { + const sha3sum = (data: ArrayBuffer): ArrayBuffer => { + const sha3 = new Sha3_256(); + return sha3.update(data).digest(); + }; + + for (const [input, output] of testSetSha3_256) { + const rawOutput = hex.decodeString(output); + assertEquals(sha3sum(s2b(input)), rawOutput); + } +}); + +test("[hash/sha3] testSha3-256String", () => { + const sha3sum = (data: string): string => { + const sha3 = new Sha3_256(); + return sha3.update(data).toString(); + }; + + for (const [input, output] of testSetSha3_256) { + assertEquals(sha3sum(input), output); + } +}); + +test("[hash/sha3] testSha3-384Raw", () => { + const sha3sum = (data: ArrayBuffer): ArrayBuffer => { + const sha3 = new Sha3_384(); + return sha3.update(data).digest(); + }; + + for (const [input, output] of testSetSha3_384) { + const rawOutput = hex.decodeString(output); + assertEquals(sha3sum(s2b(input)), rawOutput); + } +}); + +test("[hash/sha3] testSha3-384String", () => { + const sha3sum = (data: string): string => { + const sha3 = new Sha3_384(); + return sha3.update(data).toString(); + }; + + for (const [input, output] of testSetSha3_384) { + assertEquals(sha3sum(input), output); + } +}); + +test("[hash/sha3] testSha3-512Raw", () => { + const sha3sum = (data: ArrayBuffer): ArrayBuffer => { + const sha3 = new Sha3_512(); + return sha3.update(data).digest(); + }; + + for (const [input, output] of testSetSha3_512) { + const rawOutput = hex.decodeString(output); + assertEquals(sha3sum(s2b(input)), rawOutput); + } +}); + +test("[hash/sha3] testSha3-512String", () => { + const sha3sum = (data: string): string => { + const sha3 = new Sha3_512(); + return sha3.update(data).toString(); + }; + + for (const [input, output] of testSetSha3_512) { + assertEquals(sha3sum(input), output); + } +}); + +test("[hash/sha3] testKeccak-224Raw", () => { + const keccakSum = (data: ArrayBuffer): ArrayBuffer => { + const keccak = new Keccak224(); + return keccak.update(data).digest(); + }; + + for (const [input, output] of testSetKeccak224) { + const rawOutput = hex.decodeString(output); + assertEquals(keccakSum(s2b(input)), rawOutput); + } +}); + +test("[hash/sha3] testKeccak-224String", () => { + const keccakSum = (data: string): string => { + const keccak = new Keccak224(); + return keccak.update(data).toString(); + }; + + for (const [input, output] of testSetKeccak224) { + assertEquals(keccakSum(input), output); + } +}); + +test("[hash/sha3] testKeccak-256Raw", () => { + const keccakSum = (data: ArrayBuffer): ArrayBuffer => { + const keccak = new Keccak256(); + return keccak.update(data).digest(); + }; + + for (const [input, output] of testSetKeccak256) { + const rawOutput = hex.decodeString(output); + assertEquals(keccakSum(s2b(input)), rawOutput); + } +}); + +test("[hash/sha3] testKeccak-256String", () => { + const keccakSum = (data: string): string => { + const keccak = new Keccak256(); + return keccak.update(data).toString(); + }; + + for (const [input, output] of testSetKeccak256) { + assertEquals(keccakSum(input), output); + } +}); + +test("[hash/sha3] testKeccak-384Raw", () => { + const keccakSum = (data: ArrayBuffer): ArrayBuffer => { + const keccak = new Keccak384(); + return keccak.update(data).digest(); + }; + + for (const [input, output] of testSetKeccak384) { + const rawOutput = hex.decodeString(output); + assertEquals(keccakSum(s2b(input)), rawOutput); + } +}); + +test("[hash/sha3] testKeccak-384String", () => { + const keccakSum = (data: string): string => { + const keccak = new Keccak384(); + return keccak.update(data).toString(); + }; + + for (const [input, output] of testSetKeccak384) { + assertEquals(keccakSum(input), output); + } +}); + +test("[hash/sha3] testKeccak-512Raw", () => { + const keccakSum = (data: ArrayBuffer): ArrayBuffer => { + const keccak = new Keccak512(); + return keccak.update(data).digest(); + }; + + for (const [input, output] of testSetKeccak512) { + const rawOutput = hex.decodeString(output); + assertEquals(keccakSum(s2b(input)), rawOutput); + } +}); + +test("[hash/sha3] testKeccak-512String", () => { + const keccakSum = (data: string): string => { + const keccak = new Keccak512(); + return keccak.update(data).toString(); + }; + + for (const [input, output] of testSetKeccak512) { + assertEquals(keccakSum(input), output); + } +}); + +test("[hash/sha3] testSHAKE-128Raw", () => { + const shakeSum = (data: ArrayBuffer): ArrayBuffer => { + const shake = new Shake128(128); + return shake.update(data).digest(); + }; + + for (const [input, output] of testSetShake128) { + const rawOutput = hex.decodeString(output); + assertEquals(shakeSum(s2b(input)), rawOutput); + } +}); + +test("[hash/sha3] testSHAKE-128String", () => { + const shakeSum = (data: string): string => { + const shake = new Shake128(128); + return shake.update(data).toString(); + }; + + for (const [input, output] of testSetShake128) { + assertEquals(shakeSum(input), output); + } +}); + +test("[hash/sha3] testSHAKE-128-224Raw", () => { + const shakeSum = (data: ArrayBuffer): ArrayBuffer => { + const shake = new Shake128(224); + return shake.update(data).digest(); + }; + + for (const [input, output] of testSetShake128_224) { + const rawOutput = hex.decodeString(output); + assertEquals(shakeSum(s2b(input)), rawOutput); + } +}); + +test("[hash/sha3] testSHAKE-128-224String", () => { + const shakeSum = (data: string): string => { + const shake = new Shake128(224); + return shake.update(data).toString(); + }; + + for (const [input, output] of testSetShake128_224) { + assertEquals(shakeSum(input), output); + } +}); + +test("[hash/sha3] testSHAKE-128-2048", () => { + const shakeSum = (data: string): string => { + const shake = new Shake128(2048); + return shake.update(data).toString(); + }; + + for (const [input, output] of testSetShake128_2048) { + assertEquals(shakeSum(input), output); + } +}); + +test("[hash/sha3] testSHAKE-256", () => { + const shakeSum = (data: string): string => { + const shake = new Shake256(256); + return shake.update(data).toString(); + }; + + for (const [input, output] of testSetShake256) { + assertEquals(shakeSum(input), output); + } +}); + +test("[hash/sha3] testSHAKE-256-128", () => { + const shakeSum = (data: string): string => { + const shake = new Shake256(128); + return shake.update(data).toString(); + }; + + for (const [input, output] of testSetShake256_128) { + assertEquals(shakeSum(input), output); + } +}); + +test("[hash/sha3] testSHAKE-256-384", () => { + const shakeSum = (data: string): string => { + const shake = new Shake256(384); + return shake.update(data).toString(); + }; + + for (const [input, output] of testSetShake256_384) { + assertEquals(shakeSum(input), output); + } +}); + +test("[hash/sha3] testSHAKE-256-512", () => { + const shakeSum = (data: string): string => { + const shake = new Shake256(512); + return shake.update(data).toString(); + }; + + for (const [input, output] of testSetShake256_512) { + assertEquals(shakeSum(input), output); + } +}); + +test("[hash/sha3] testSha3-256Chain", () => { + const sha3 = new Sha3_256(); + const output = sha3 + .update(s2b("a")) + .update(s2b("b")) + .update(s2b("c")) + .toString(); + + assertEquals( + output, + "3a985da74fe225b2045c172d6bd390bd855f086e3e9d525b46bfe24511431532" + ); +}); + +test("[hash/sha3] testSha3UpdateFinalized", () => { + assertThrows( + () => { + const sha3 = new Sha3_256(); + const hash = sha3.update(s2b("a")).digest(); + const hash2 = sha3.update(s2b("a")).digest(); + assertEquals(hash, hash2); + }, + Error, + "sha3: cannot update already finalized hash" + ); +}); diff --git a/std/hash/sha512.ts b/std/hash/sha512.ts new file mode 100644 index 00000000000000..b55069f4d7693e --- /dev/null +++ b/std/hash/sha512.ts @@ -0,0 +1,791 @@ +/* + * [js-sha512]{@link https://github.com/emn178/js-sha512} + * + * @version 0.8.0 + * @author Chen, Yi-Cyuan [emn178@gmail.com] + * @copyright Chen, Yi-Cyuan 2014-2018 + * @license MIT + */ + +export type Message = string | number[] | ArrayBuffer; + +// prettier-ignore +const HEX_CHARS = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f"] as const; +const EXTRA = [-2147483648, 8388608, 32768, 128] as const; +const SHIFT = [24, 16, 8, 0] as const; +// prettier-ignore +const K = [ + 0x428a2f98, 0xd728ae22, 0x71374491, 0x23ef65cd, 0xb5c0fbcf, 0xec4d3b2f, 0xe9b5dba5, 0x8189dbbc, 0x3956c25b, + 0xf348b538, 0x59f111f1, 0xb605d019, 0x923f82a4, 0xaf194f9b, 0xab1c5ed5, 0xda6d8118, 0xd807aa98, 0xa3030242, + 0x12835b01, 0x45706fbe, 0x243185be, 0x4ee4b28c, 0x550c7dc3, 0xd5ffb4e2, 0x72be5d74, 0xf27b896f, 0x80deb1fe, + 0x3b1696b1, 0x9bdc06a7, 0x25c71235, 0xc19bf174, 0xcf692694, 0xe49b69c1, 0x9ef14ad2, 0xefbe4786, 0x384f25e3, + 0x0fc19dc6, 0x8b8cd5b5, 0x240ca1cc, 0x77ac9c65, 0x2de92c6f, 0x592b0275, 0x4a7484aa, 0x6ea6e483, 0x5cb0a9dc, + 0xbd41fbd4, 0x76f988da, 0x831153b5, 0x983e5152, 0xee66dfab, 0xa831c66d, 0x2db43210, 0xb00327c8, 0x98fb213f, + 0xbf597fc7, 0xbeef0ee4, 0xc6e00bf3, 0x3da88fc2, 0xd5a79147, 0x930aa725, 0x06ca6351, 0xe003826f, 0x14292967, + 0x0a0e6e70, 0x27b70a85, 0x46d22ffc, 0x2e1b2138, 0x5c26c926, 0x4d2c6dfc, 0x5ac42aed, 0x53380d13, 0x9d95b3df, + 0x650a7354, 0x8baf63de, 0x766a0abb, 0x3c77b2a8, 0x81c2c92e, 0x47edaee6, 0x92722c85, 0x1482353b, 0xa2bfe8a1, + 0x4cf10364, 0xa81a664b, 0xbc423001, 0xc24b8b70, 0xd0f89791, 0xc76c51a3, 0x0654be30, 0xd192e819, 0xd6ef5218, + 0xd6990624, 0x5565a910, 0xf40e3585, 0x5771202a, 0x106aa070, 0x32bbd1b8, 0x19a4c116, 0xb8d2d0c8, 0x1e376c08, + 0x5141ab53, 0x2748774c, 0xdf8eeb99, 0x34b0bcb5, 0xe19b48a8, 0x391c0cb3, 0xc5c95a63, 0x4ed8aa4a, 0xe3418acb, + 0x5b9cca4f, 0x7763e373, 0x682e6ff3, 0xd6b2b8a3, 0x748f82ee, 0x5defb2fc, 0x78a5636f, 0x43172f60, 0x84c87814, + 0xa1f0ab72, 0x8cc70208, 0x1a6439ec, 0x90befffa, 0x23631e28, 0xa4506ceb, 0xde82bde9, 0xbef9a3f7, 0xb2c67915, + 0xc67178f2, 0xe372532b, 0xca273ece, 0xea26619c, 0xd186b8c7, 0x21c0c207, 0xeada7dd6, 0xcde0eb1e, 0xf57d4f7f, + 0xee6ed178, 0x06f067aa, 0x72176fba, 0x0a637dc5, 0xa2c898a6, 0x113f9804, 0xbef90dae, 0x1b710b35, 0x131c471b, + 0x28db77f5, 0x23047d84, 0x32caab7b, 0x40c72493, 0x3c9ebe0a, 0x15c9bebc, 0x431d67c4, 0x9c100d4c, 0x4cc5d4be, + 0xcb3e42b6, 0x597f299c, 0xfc657e2a, 0x5fcb6fab, 0x3ad6faec, 0x6c44198c, 0x4a475817 +] as const; + +const blocks: number[] = []; + +// prettier-ignore +export class Sha512 { + #blocks!: number[]; + #block!: number; + #bits!: number; + #start!: number; + #bytes!: number; + #hBytes!: number; + #lastByteIndex = 0; + #finalized!: boolean; + #hashed!: boolean; + #h0h!: number; + #h0l!: number; + #h1h!: number; + #h1l!: number; + #h2h!: number; + #h2l!: number; + #h3h!: number; + #h3l!: number; + #h4h!: number; + #h4l!: number; + #h5h!: number; + #h5l!: number; + #h6h!: number; + #h6l!: number; + #h7h!: number; + #h7l!: number; + + constructor(bits = 512, sharedMemory = false) { + this.init(bits, sharedMemory); + } + + protected init(bits: number, sharedMemory: boolean): void { + if (sharedMemory) { + blocks[0] = blocks[1] = blocks[2] = blocks[3] = blocks[4] = blocks[5] = blocks[6] = blocks[7] = blocks[8] = + blocks[9] = blocks[10] = blocks[11] = blocks[12] = blocks[13] = blocks[14] = blocks[15] = blocks[16] = + blocks[17] = blocks[18] = blocks[19] = blocks[20] = blocks[21] = blocks[22] = blocks[23] = blocks[24] = + blocks[25] = blocks[26] = blocks[27] = blocks[28] = blocks[29] = blocks[30] = blocks[31] = blocks[32] = 0; + this.#blocks = blocks; + } else { + this.#blocks = + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + } + if (bits === 224) { + this.#h0h = 0x8c3d37c8; + this.#h0l = 0x19544da2; + this.#h1h = 0x73e19966; + this.#h1l = 0x89dcd4d6; + this.#h2h = 0x1dfab7ae; + this.#h2l = 0x32ff9c82; + this.#h3h = 0x679dd514; + this.#h3l = 0x582f9fcf; + this.#h4h = 0x0f6d2b69; + this.#h4l = 0x7bd44da8; + this.#h5h = 0x77e36f73; + this.#h5l = 0x04c48942; + this.#h6h = 0x3f9d85a8; + this.#h6l = 0x6a1d36c8; + this.#h7h = 0x1112e6ad; + this.#h7l = 0x91d692a1; + } else if (bits === 256) { + this.#h0h = 0x22312194; + this.#h0l = 0xfc2bf72c; + this.#h1h = 0x9f555fa3; + this.#h1l = 0xc84c64c2; + this.#h2h = 0x2393b86b; + this.#h2l = 0x6f53b151; + this.#h3h = 0x96387719; + this.#h3l = 0x5940eabd; + this.#h4h = 0x96283ee2; + this.#h4l = 0xa88effe3; + this.#h5h = 0xbe5e1e25; + this.#h5l = 0x53863992; + this.#h6h = 0x2b0199fc; + this.#h6l = 0x2c85b8aa; + this.#h7h = 0x0eb72ddc; + this.#h7l = 0x81c52ca2; + } else if (bits === 384) { + this.#h0h = 0xcbbb9d5d; + this.#h0l = 0xc1059ed8; + this.#h1h = 0x629a292a; + this.#h1l = 0x367cd507; + this.#h2h = 0x9159015a; + this.#h2l = 0x3070dd17; + this.#h3h = 0x152fecd8; + this.#h3l = 0xf70e5939; + this.#h4h = 0x67332667; + this.#h4l = 0xffc00b31; + this.#h5h = 0x8eb44a87; + this.#h5l = 0x68581511; + this.#h6h = 0xdb0c2e0d; + this.#h6l = 0x64f98fa7; + this.#h7h = 0x47b5481d; + this.#h7l = 0xbefa4fa4; + } else { // 512 + this.#h0h = 0x6a09e667; + this.#h0l = 0xf3bcc908; + this.#h1h = 0xbb67ae85; + this.#h1l = 0x84caa73b; + this.#h2h = 0x3c6ef372; + this.#h2l = 0xfe94f82b; + this.#h3h = 0xa54ff53a; + this.#h3l = 0x5f1d36f1; + this.#h4h = 0x510e527f; + this.#h4l = 0xade682d1; + this.#h5h = 0x9b05688c; + this.#h5l = 0x2b3e6c1f; + this.#h6h = 0x1f83d9ab; + this.#h6l = 0xfb41bd6b; + this.#h7h = 0x5be0cd19; + this.#h7l = 0x137e2179; + } + this.#bits = bits; + this.#block = this.#start = this.#bytes = this.#hBytes = 0; + this.#finalized = this.#hashed = false; + } + + update(message: Message): this { + if (this.#finalized) { + return this; + } + let msg: string | number[] | Uint8Array; + if (message instanceof ArrayBuffer) { + msg = new Uint8Array(message); + } else { + msg = message; + } + const length = msg.length; + const blocks = this.#blocks; + let index = 0; + while (index < length) { + let i: number; + if (this.#hashed) { + this.#hashed = false; + blocks[0] = this.#block; + blocks[1] = blocks[2] = blocks[3] = blocks[4] = blocks[5] = blocks[6] = blocks[7] = blocks[8] = + blocks[9] = blocks[10] = blocks[11] = blocks[12] = blocks[13] = blocks[14] = blocks[15] = blocks[16] = + blocks[17] = blocks[18] = blocks[19] = blocks[20] = blocks[21] = blocks[22] = blocks[23] = blocks[24] = + blocks[25] = blocks[26] = blocks[27] = blocks[28] = blocks[29] = blocks[30] = blocks[31] = blocks[32] = 0; + } + if (typeof msg !== "string") { + for (i = this.#start; index < length && i < 128; ++index) { + blocks[i >> 2] |= msg[index] << SHIFT[i++ & 3]; + } + } else { + for (i = this.#start; index < length && i < 128; ++index) { + let code = msg.charCodeAt(index); + if (code < 0x80) { + blocks[i >> 2] |= code << SHIFT[i++ & 3]; + } else if (code < 0x800) { + blocks[i >> 2] |= (0xc0 | (code >> 6)) << SHIFT[i++ & 3]; + blocks[i >> 2] |= (0x80 | (code & 0x3f)) << SHIFT[i++ & 3]; + } else if (code < 0xd800 || code >= 0xe000) { + blocks[i >> 2] |= (0xe0 | (code >> 12)) << SHIFT[i++ & 3]; + blocks[i >> 2] |= (0x80 | ((code >> 6) & 0x3f)) << SHIFT[i++ & 3]; + blocks[i >> 2] |= (0x80 | (code & 0x3f)) << SHIFT[i++ & 3]; + } else { + code = 0x10000 + (((code & 0x3ff) << 10) | (msg.charCodeAt(++index) & 0x3ff)); + blocks[i >> 2] |= (0xf0 | (code >> 18)) << SHIFT[i++ & 3]; + blocks[i >> 2] |= (0x80 | ((code >> 12) & 0x3f)) << SHIFT[i++ & 3]; + blocks[i >> 2] |= (0x80 | ((code >> 6) & 0x3f)) << SHIFT[i++ & 3]; + blocks[i >> 2] |= (0x80 | (code & 0x3f)) << SHIFT[i++ & 3]; + } + } + } + this.#lastByteIndex = i; + this.#bytes += i - this.#start; + if (i >= 128) { + this.#block = blocks[32]; + this.#start = i - 128; + this.hash(); + this.#hashed = true; + } else { + this.#start = i; + } + } + if (this.#bytes > 4294967295) { + this.#hBytes += (this.#bytes / 4294967296) << 0; + this.#bytes = this.#bytes % 4294967296; + } + return this; + } + + protected finalize(): void { + if (this.#finalized) { + return; + } + this.#finalized = true; + const blocks = this.#blocks; + const i = this.#lastByteIndex; + blocks[32] = this.#block; + blocks[i >> 2] |= EXTRA[i & 3]; + this.#block = blocks[32]; + if (i >= 112) { + if (!this.#hashed) { + this.hash(); + } + blocks[0] = this.#block; + blocks[1] = blocks[2] = blocks[3] = blocks[4] = blocks[5] = blocks[6] = blocks[7] = blocks[8] = + blocks[9] =blocks[10] = blocks[11] = blocks[12] = blocks[13] = blocks[14] = blocks[15] = blocks[16] = + blocks[17] = blocks[18] = blocks[19] = blocks[20] = blocks[21] = blocks[22] = blocks[23] = blocks[24] = + blocks[25] = blocks[26] = blocks[27] = blocks[28] = blocks[29] = blocks[30] = blocks[31] = blocks[32] = 0; + } + blocks[30] = (this.#hBytes << 3) | (this.#bytes >>> 29); + blocks[31] = this.#bytes << 3; + this.hash(); + } + + protected hash(): void { + const + h0h = this.#h0h, h0l = this.#h0l, h1h = this.#h1h, h1l = this.#h1l, h2h = this.#h2h, h2l = this.#h2l, + h3h = this.#h3h, h3l = this.#h3l, h4h = this.#h4h, h4l = this.#h4l, h5h = this.#h5h, h5l = this.#h5l, + h6h = this.#h6h, h6l = this.#h6l, h7h = this.#h7h, h7l = this.#h7l; + + let s0h, s0l, s1h, s1l, c1, c2, c3, c4, abh, abl, dah, dal, cdh, cdl, bch, bcl, majh, majl, + t1h, t1l, t2h, t2l, chh, chl: number; + + const blocks = this.#blocks; + + for (let j = 32; j < 160; j += 2) { + t1h = blocks[j - 30]; + t1l = blocks[j - 29]; + s0h = ((t1h >>> 1) | (t1l << 31)) ^ ((t1h >>> 8) | (t1l << 24)) ^ (t1h >>> 7); + s0l = ((t1l >>> 1) | (t1h << 31)) ^ ((t1l >>> 8) | (t1h << 24)) ^ ((t1l >>> 7) | (t1h << 25)); + + t1h = blocks[j - 4]; + t1l = blocks[j - 3]; + s1h = ((t1h >>> 19) | (t1l << 13)) ^ ((t1l >>> 29) | (t1h << 3)) ^ (t1h >>> 6); + s1l = ((t1l >>> 19) | (t1h << 13)) ^ ((t1h >>> 29) | (t1l << 3)) ^ ((t1l >>> 6) | (t1h << 26)); + + t1h = blocks[j - 32]; + t1l = blocks[j - 31]; + t2h = blocks[j - 14]; + t2l = blocks[j - 13]; + + c1 = (t2l & 0xffff) + (t1l & 0xffff) + (s0l & 0xffff) + (s1l & 0xffff); + c2 = (t2l >>> 16) + (t1l >>> 16) + (s0l >>> 16) + (s1l >>> 16) + (c1 >>> 16); + c3 = (t2h & 0xffff) + (t1h & 0xffff) + (s0h & 0xffff) + (s1h & 0xffff) + (c2 >>> 16); + c4 = (t2h >>> 16) + (t1h >>> 16) + (s0h >>> 16) + (s1h >>> 16) + (c3 >>> 16); + + blocks[j] = (c4 << 16) | (c3 & 0xffff); + blocks[j + 1] = (c2 << 16) | (c1 & 0xffff); + } + + let ah = h0h, al = h0l, bh = h1h, bl = h1l, ch = h2h, cl = h2l, dh = h3h, dl = h3l, eh = h4h, el = h4l, + fh = h5h, fl = h5l, gh = h6h, gl = h6l, hh = h7h, hl = h7l; + + bch = bh & ch; + bcl = bl & cl; + + for (let j = 0; j < 160; j += 8) { + s0h = ((ah >>> 28) | (al << 4)) ^ ((al >>> 2) | (ah << 30)) ^ ((al >>> 7) | (ah << 25)); + s0l = ((al >>> 28) | (ah << 4)) ^ ((ah >>> 2) | (al << 30)) ^ ((ah >>> 7) | (al << 25)); + + s1h = ((eh >>> 14) | (el << 18)) ^ ((eh >>> 18) | (el << 14)) ^ ((el >>> 9) | (eh << 23)); + s1l = ((el >>> 14) | (eh << 18)) ^ ((el >>> 18) | (eh << 14)) ^ ((eh >>> 9) | (el << 23)); + + abh = ah & bh; + abl = al & bl; + majh = abh ^ (ah & ch) ^ bch; + majl = abl ^ (al & cl) ^ bcl; + + chh = (eh & fh) ^ (~eh & gh); + chl = (el & fl) ^ (~el & gl); + + t1h = blocks[j]; + t1l = blocks[j + 1]; + t2h = K[j]; + t2l = K[j + 1]; + + c1 = (t2l & 0xffff) + (t1l & 0xffff) + (chl & 0xffff) + (s1l & 0xffff) + (hl & 0xffff); + c2 = (t2l >>> 16) + (t1l >>> 16) + (chl >>> 16) + (s1l >>> 16) + (hl >>> 16) + (c1 >>> 16); + c3 = (t2h & 0xffff) + (t1h & 0xffff) + (chh & 0xffff) + (s1h & 0xffff) + (hh & 0xffff) + (c2 >>> 16); + c4 = (t2h >>> 16) + (t1h >>> 16) + (chh >>> 16) + (s1h >>> 16) + (hh >>> 16) + (c3 >>> 16); + + t1h = (c4 << 16) | (c3 & 0xffff); + t1l = (c2 << 16) | (c1 & 0xffff); + + c1 = (majl & 0xffff) + (s0l & 0xffff); + c2 = (majl >>> 16) + (s0l >>> 16) + (c1 >>> 16); + c3 = (majh & 0xffff) + (s0h & 0xffff) + (c2 >>> 16); + c4 = (majh >>> 16) + (s0h >>> 16) + (c3 >>> 16); + + t2h = (c4 << 16) | (c3 & 0xffff); + t2l = (c2 << 16) | (c1 & 0xffff); + + c1 = (dl & 0xffff) + (t1l & 0xffff); + c2 = (dl >>> 16) + (t1l >>> 16) + (c1 >>> 16); + c3 = (dh & 0xffff) + (t1h & 0xffff) + (c2 >>> 16); + c4 = (dh >>> 16) + (t1h >>> 16) + (c3 >>> 16); + + hh = (c4 << 16) | (c3 & 0xffff); + hl = (c2 << 16) | (c1 & 0xffff); + + c1 = (t2l & 0xffff) + (t1l & 0xffff); + c2 = (t2l >>> 16) + (t1l >>> 16) + (c1 >>> 16); + c3 = (t2h & 0xffff) + (t1h & 0xffff) + (c2 >>> 16); + c4 = (t2h >>> 16) + (t1h >>> 16) + (c3 >>> 16); + + dh = (c4 << 16) | (c3 & 0xffff); + dl = (c2 << 16) | (c1 & 0xffff); + + s0h = ((dh >>> 28) | (dl << 4)) ^ ((dl >>> 2) | (dh << 30)) ^ ((dl >>> 7) | (dh << 25)); + s0l = ((dl >>> 28) | (dh << 4)) ^ ((dh >>> 2) | (dl << 30)) ^ ((dh >>> 7) | (dl << 25)); + + s1h = ((hh >>> 14) | (hl << 18)) ^ ((hh >>> 18) | (hl << 14)) ^ ((hl >>> 9) | (hh << 23)); + s1l = ((hl >>> 14) | (hh << 18)) ^ ((hl >>> 18) | (hh << 14)) ^ ((hh >>> 9) | (hl << 23)); + + dah = dh & ah; + dal = dl & al; + majh = dah ^ (dh & bh) ^ abh; + majl = dal ^ (dl & bl) ^ abl; + + chh = (hh & eh) ^ (~hh & fh); + chl = (hl & el) ^ (~hl & fl); + + t1h = blocks[j + 2]; + t1l = blocks[j + 3]; + t2h = K[j + 2]; + t2l = K[j + 3]; + + c1 = (t2l & 0xffff) + (t1l & 0xffff) + (chl & 0xffff) + (s1l & 0xffff) + (gl & 0xffff); + c2 = (t2l >>> 16) + (t1l >>> 16) + (chl >>> 16) + (s1l >>> 16) + (gl >>> 16) + (c1 >>> 16); + c3 = (t2h & 0xffff) + (t1h & 0xffff) + (chh & 0xffff) + (s1h & 0xffff) + (gh & 0xffff) + (c2 >>> 16); + c4 = (t2h >>> 16) + (t1h >>> 16) + (chh >>> 16) + (s1h >>> 16) + (gh >>> 16) + (c3 >>> 16); + + t1h = (c4 << 16) | (c3 & 0xffff); + t1l = (c2 << 16) | (c1 & 0xffff); + + c1 = (majl & 0xffff) + (s0l & 0xffff); + c2 = (majl >>> 16) + (s0l >>> 16) + (c1 >>> 16); + c3 = (majh & 0xffff) + (s0h & 0xffff) + (c2 >>> 16); + c4 = (majh >>> 16) + (s0h >>> 16) + (c3 >>> 16); + + t2h = (c4 << 16) | (c3 & 0xffff); + t2l = (c2 << 16) | (c1 & 0xffff); + + c1 = (cl & 0xffff) + (t1l & 0xffff); + c2 = (cl >>> 16) + (t1l >>> 16) + (c1 >>> 16); + c3 = (ch & 0xffff) + (t1h & 0xffff) + (c2 >>> 16); + c4 = (ch >>> 16) + (t1h >>> 16) + (c3 >>> 16); + + gh = (c4 << 16) | (c3 & 0xffff); + gl = (c2 << 16) | (c1 & 0xffff); + + c1 = (t2l & 0xffff) + (t1l & 0xffff); + c2 = (t2l >>> 16) + (t1l >>> 16) + (c1 >>> 16); + c3 = (t2h & 0xffff) + (t1h & 0xffff) + (c2 >>> 16); + c4 = (t2h >>> 16) + (t1h >>> 16) + (c3 >>> 16); + + ch = (c4 << 16) | (c3 & 0xffff); + cl = (c2 << 16) | (c1 & 0xffff); + + s0h = ((ch >>> 28) | (cl << 4)) ^ ((cl >>> 2) | (ch << 30)) ^ ((cl >>> 7) | (ch << 25)); + s0l = ((cl >>> 28) | (ch << 4)) ^ ((ch >>> 2) | (cl << 30)) ^ ((ch >>> 7) | (cl << 25)); + + s1h = ((gh >>> 14) | (gl << 18)) ^ ((gh >>> 18) | (gl << 14)) ^ ((gl >>> 9) | (gh << 23)); + s1l = ((gl >>> 14) | (gh << 18)) ^ ((gl >>> 18) | (gh << 14)) ^ ((gh >>> 9) | (gl << 23)); + + cdh = ch & dh; + cdl = cl & dl; + majh = cdh ^ (ch & ah) ^ dah; + majl = cdl ^ (cl & al) ^ dal; + + chh = (gh & hh) ^ (~gh & eh); + chl = (gl & hl) ^ (~gl & el); + + t1h = blocks[j + 4]; + t1l = blocks[j + 5]; + t2h = K[j + 4]; + t2l = K[j + 5]; + + c1 = (t2l & 0xffff) + (t1l & 0xffff) + (chl & 0xffff) + (s1l & 0xffff) + (fl & 0xffff); + c2 = (t2l >>> 16) + (t1l >>> 16) + (chl >>> 16) + (s1l >>> 16) + (fl >>> 16) + (c1 >>> 16); + c3 = (t2h & 0xffff) + (t1h & 0xffff) + (chh & 0xffff) + (s1h & 0xffff) + (fh & 0xffff) + (c2 >>> 16); + c4 = (t2h >>> 16) + (t1h >>> 16) + (chh >>> 16) + (s1h >>> 16) + (fh >>> 16) + (c3 >>> 16); + + t1h = (c4 << 16) | (c3 & 0xffff); + t1l = (c2 << 16) | (c1 & 0xffff); + + c1 = (majl & 0xffff) + (s0l & 0xffff); + c2 = (majl >>> 16) + (s0l >>> 16) + (c1 >>> 16); + c3 = (majh & 0xffff) + (s0h & 0xffff) + (c2 >>> 16); + c4 = (majh >>> 16) + (s0h >>> 16) + (c3 >>> 16); + + t2h = (c4 << 16) | (c3 & 0xffff); + t2l = (c2 << 16) | (c1 & 0xffff); + + c1 = (bl & 0xffff) + (t1l & 0xffff); + c2 = (bl >>> 16) + (t1l >>> 16) + (c1 >>> 16); + c3 = (bh & 0xffff) + (t1h & 0xffff) + (c2 >>> 16); + c4 = (bh >>> 16) + (t1h >>> 16) + (c3 >>> 16); + + fh = (c4 << 16) | (c3 & 0xffff); + fl = (c2 << 16) | (c1 & 0xffff); + + c1 = (t2l & 0xffff) + (t1l & 0xffff); + c2 = (t2l >>> 16) + (t1l >>> 16) + (c1 >>> 16); + c3 = (t2h & 0xffff) + (t1h & 0xffff) + (c2 >>> 16); + c4 = (t2h >>> 16) + (t1h >>> 16) + (c3 >>> 16); + + bh = (c4 << 16) | (c3 & 0xffff); + bl = (c2 << 16) | (c1 & 0xffff); + + s0h = ((bh >>> 28) | (bl << 4)) ^ ((bl >>> 2) | (bh << 30)) ^ ((bl >>> 7) | (bh << 25)); + s0l = ((bl >>> 28) | (bh << 4)) ^ ((bh >>> 2) | (bl << 30)) ^ ((bh >>> 7) | (bl << 25)); + + s1h = ((fh >>> 14) | (fl << 18)) ^ ((fh >>> 18) | (fl << 14)) ^ ((fl >>> 9) | (fh << 23)); + s1l = ((fl >>> 14) | (fh << 18)) ^ ((fl >>> 18) | (fh << 14)) ^ ((fh >>> 9) | (fl << 23)); + + bch = bh & ch; + bcl = bl & cl; + majh = bch ^ (bh & dh) ^ cdh; + majl = bcl ^ (bl & dl) ^ cdl; + + chh = (fh & gh) ^ (~fh & hh); + chl = (fl & gl) ^ (~fl & hl); + + t1h = blocks[j + 6]; + t1l = blocks[j + 7]; + t2h = K[j + 6]; + t2l = K[j + 7]; + + c1 = (t2l & 0xffff) + (t1l & 0xffff) + (chl & 0xffff) + (s1l & 0xffff) + (el & 0xffff); + c2 = (t2l >>> 16) + (t1l >>> 16) + (chl >>> 16) + (s1l >>> 16) + (el >>> 16) + (c1 >>> 16); + c3 = (t2h & 0xffff) + (t1h & 0xffff) + (chh & 0xffff) + (s1h & 0xffff) + (eh & 0xffff) + (c2 >>> 16); + c4 = (t2h >>> 16) + (t1h >>> 16) + (chh >>> 16) + (s1h >>> 16) + (eh >>> 16) + (c3 >>> 16); + + t1h = (c4 << 16) | (c3 & 0xffff); + t1l = (c2 << 16) | (c1 & 0xffff); + + c1 = (majl & 0xffff) + (s0l & 0xffff); + c2 = (majl >>> 16) + (s0l >>> 16) + (c1 >>> 16); + c3 = (majh & 0xffff) + (s0h & 0xffff) + (c2 >>> 16); + c4 = (majh >>> 16) + (s0h >>> 16) + (c3 >>> 16); + + t2h = (c4 << 16) | (c3 & 0xffff); + t2l = (c2 << 16) | (c1 & 0xffff); + + c1 = (al & 0xffff) + (t1l & 0xffff); + c2 = (al >>> 16) + (t1l >>> 16) + (c1 >>> 16); + c3 = (ah & 0xffff) + (t1h & 0xffff) + (c2 >>> 16); + c4 = (ah >>> 16) + (t1h >>> 16) + (c3 >>> 16); + + eh = (c4 << 16) | (c3 & 0xffff); + el = (c2 << 16) | (c1 & 0xffff); + + c1 = (t2l & 0xffff) + (t1l & 0xffff); + c2 = (t2l >>> 16) + (t1l >>> 16) + (c1 >>> 16); + c3 = (t2h & 0xffff) + (t1h & 0xffff) + (c2 >>> 16); + c4 = (t2h >>> 16) + (t1h >>> 16) + (c3 >>> 16); + + ah = (c4 << 16) | (c3 & 0xffff); + al = (c2 << 16) | (c1 & 0xffff); + } + + c1 = (h0l & 0xffff) + (al & 0xffff); + c2 = (h0l >>> 16) + (al >>> 16) + (c1 >>> 16); + c3 = (h0h & 0xffff) + (ah & 0xffff) + (c2 >>> 16); + c4 = (h0h >>> 16) + (ah >>> 16) + (c3 >>> 16); + + this.#h0h = (c4 << 16) | (c3 & 0xffff); + this.#h0l = (c2 << 16) | (c1 & 0xffff); + + c1 = (h1l & 0xffff) + (bl & 0xffff); + c2 = (h1l >>> 16) + (bl >>> 16) + (c1 >>> 16); + c3 = (h1h & 0xffff) + (bh & 0xffff) + (c2 >>> 16); + c4 = (h1h >>> 16) + (bh >>> 16) + (c3 >>> 16); + + this.#h1h = (c4 << 16) | (c3 & 0xffff); + this.#h1l = (c2 << 16) | (c1 & 0xffff); + + c1 = (h2l & 0xffff) + (cl & 0xffff); + c2 = (h2l >>> 16) + (cl >>> 16) + (c1 >>> 16); + c3 = (h2h & 0xffff) + (ch & 0xffff) + (c2 >>> 16); + c4 = (h2h >>> 16) + (ch >>> 16) + (c3 >>> 16); + + this.#h2h = (c4 << 16) | (c3 & 0xffff); + this.#h2l = (c2 << 16) | (c1 & 0xffff); + + c1 = (h3l & 0xffff) + (dl & 0xffff); + c2 = (h3l >>> 16) + (dl >>> 16) + (c1 >>> 16); + c3 = (h3h & 0xffff) + (dh & 0xffff) + (c2 >>> 16); + c4 = (h3h >>> 16) + (dh >>> 16) + (c3 >>> 16); + + this.#h3h = (c4 << 16) | (c3 & 0xffff); + this.#h3l = (c2 << 16) | (c1 & 0xffff); + + c1 = (h4l & 0xffff) + (el & 0xffff); + c2 = (h4l >>> 16) + (el >>> 16) + (c1 >>> 16); + c3 = (h4h & 0xffff) + (eh & 0xffff) + (c2 >>> 16); + c4 = (h4h >>> 16) + (eh >>> 16) + (c3 >>> 16); + + this.#h4h = (c4 << 16) | (c3 & 0xffff); + this.#h4l = (c2 << 16) | (c1 & 0xffff); + + c1 = (h5l & 0xffff) + (fl & 0xffff); + c2 = (h5l >>> 16) + (fl >>> 16) + (c1 >>> 16); + c3 = (h5h & 0xffff) + (fh & 0xffff) + (c2 >>> 16); + c4 = (h5h >>> 16) + (fh >>> 16) + (c3 >>> 16); + + this.#h5h = (c4 << 16) | (c3 & 0xffff); + this.#h5l = (c2 << 16) | (c1 & 0xffff); + + c1 = (h6l & 0xffff) + (gl & 0xffff); + c2 = (h6l >>> 16) + (gl >>> 16) + (c1 >>> 16); + c3 = (h6h & 0xffff) + (gh & 0xffff) + (c2 >>> 16); + c4 = (h6h >>> 16) + (gh >>> 16) + (c3 >>> 16); + + this.#h6h = (c4 << 16) | (c3 & 0xffff); + this.#h6l = (c2 << 16) | (c1 & 0xffff); + + c1 = (h7l & 0xffff) + (hl & 0xffff); + c2 = (h7l >>> 16) + (hl >>> 16) + (c1 >>> 16); + c3 = (h7h & 0xffff) + (hh & 0xffff) + (c2 >>> 16); + c4 = (h7h >>> 16) + (hh >>> 16) + (c3 >>> 16); + + this.#h7h = (c4 << 16) | (c3 & 0xffff); + this.#h7l = (c2 << 16) | (c1 & 0xffff); + } + + hex(): string { + this.finalize(); + const + h0h = this.#h0h, h0l = this.#h0l, h1h = this.#h1h, h1l = this.#h1l, h2h = this.#h2h, h2l = this.#h2l, + h3h = this.#h3h, h3l = this.#h3l, h4h = this.#h4h, h4l = this.#h4l, h5h = this.#h5h, h5l = this.#h5l, + h6h = this.#h6h, h6l = this.#h6l, h7h = this.#h7h, h7l = this.#h7l, bits = this.#bits; + let hex = + HEX_CHARS[(h0h >> 28) & 0x0f] + HEX_CHARS[(h0h >> 24) & 0x0f] + + HEX_CHARS[(h0h >> 20) & 0x0f] + HEX_CHARS[(h0h >> 16) & 0x0f] + + HEX_CHARS[(h0h >> 12) & 0x0f] + HEX_CHARS[(h0h >> 8) & 0x0f] + + HEX_CHARS[(h0h >> 4) & 0x0f] + HEX_CHARS[h0h & 0x0f] + + HEX_CHARS[(h0l >> 28) & 0x0f] + HEX_CHARS[(h0l >> 24) & 0x0f] + + HEX_CHARS[(h0l >> 20) & 0x0f] + HEX_CHARS[(h0l >> 16) & 0x0f] + + HEX_CHARS[(h0l >> 12) & 0x0f] + HEX_CHARS[(h0l >> 8) & 0x0f] + + HEX_CHARS[(h0l >> 4) & 0x0f] + HEX_CHARS[h0l & 0x0f] + + HEX_CHARS[(h1h >> 28) & 0x0f] + HEX_CHARS[(h1h >> 24) & 0x0f] + + HEX_CHARS[(h1h >> 20) & 0x0f] + HEX_CHARS[(h1h >> 16) & 0x0f] + + HEX_CHARS[(h1h >> 12) & 0x0f] + HEX_CHARS[(h1h >> 8) & 0x0f] + + HEX_CHARS[(h1h >> 4) & 0x0f] + HEX_CHARS[h1h & 0x0f] + + HEX_CHARS[(h1l >> 28) & 0x0f] + HEX_CHARS[(h1l >> 24) & 0x0f] + + HEX_CHARS[(h1l >> 20) & 0x0f] + HEX_CHARS[(h1l >> 16) & 0x0f] + + HEX_CHARS[(h1l >> 12) & 0x0f] + HEX_CHARS[(h1l >> 8) & 0x0f] + + HEX_CHARS[(h1l >> 4) & 0x0f] + HEX_CHARS[h1l & 0x0f] + + HEX_CHARS[(h2h >> 28) & 0x0f] + HEX_CHARS[(h2h >> 24) & 0x0f] + + HEX_CHARS[(h2h >> 20) & 0x0f] + HEX_CHARS[(h2h >> 16) & 0x0f] + + HEX_CHARS[(h2h >> 12) & 0x0f] + HEX_CHARS[(h2h >> 8) & 0x0f] + + HEX_CHARS[(h2h >> 4) & 0x0f] + HEX_CHARS[h2h & 0x0f] + + HEX_CHARS[(h2l >> 28) & 0x0f] + HEX_CHARS[(h2l >> 24) & 0x0f] + + HEX_CHARS[(h2l >> 20) & 0x0f] + HEX_CHARS[(h2l >> 16) & 0x0f] + + HEX_CHARS[(h2l >> 12) & 0x0f] + HEX_CHARS[(h2l >> 8) & 0x0f] + + HEX_CHARS[(h2l >> 4) & 0x0f] + HEX_CHARS[h2l & 0x0f] + + HEX_CHARS[(h3h >> 28) & 0x0f] + HEX_CHARS[(h3h >> 24) & 0x0f] + + HEX_CHARS[(h3h >> 20) & 0x0f] + HEX_CHARS[(h3h >> 16) & 0x0f] + + HEX_CHARS[(h3h >> 12) & 0x0f] + HEX_CHARS[(h3h >> 8) & 0x0f] + + HEX_CHARS[(h3h >> 4) & 0x0f] + HEX_CHARS[h3h & 0x0f]; + if (bits >= 256) { + hex += + HEX_CHARS[(h3l >> 28) & 0x0f] + HEX_CHARS[(h3l >> 24) & 0x0f] + + HEX_CHARS[(h3l >> 20) & 0x0f] + HEX_CHARS[(h3l >> 16) & 0x0f] + + HEX_CHARS[(h3l >> 12) & 0x0f] + HEX_CHARS[(h3l >> 8) & 0x0f] + + HEX_CHARS[(h3l >> 4) & 0x0f] + HEX_CHARS[h3l & 0x0f]; + } + if (bits >= 384) { + hex += + HEX_CHARS[(h4h >> 28) & 0x0f] + HEX_CHARS[(h4h >> 24) & 0x0f] + + HEX_CHARS[(h4h >> 20) & 0x0f] + HEX_CHARS[(h4h >> 16) & 0x0f] + + HEX_CHARS[(h4h >> 12) & 0x0f] + HEX_CHARS[(h4h >> 8) & 0x0f] + + HEX_CHARS[(h4h >> 4) & 0x0f] + HEX_CHARS[h4h & 0x0f] + + HEX_CHARS[(h4l >> 28) & 0x0f] + HEX_CHARS[(h4l >> 24) & 0x0f] + + HEX_CHARS[(h4l >> 20) & 0x0f] + HEX_CHARS[(h4l >> 16) & 0x0f] + + HEX_CHARS[(h4l >> 12) & 0x0f] + HEX_CHARS[(h4l >> 8) & 0x0f] + + HEX_CHARS[(h4l >> 4) & 0x0f] + HEX_CHARS[h4l & 0x0f] + + HEX_CHARS[(h5h >> 28) & 0x0f] + HEX_CHARS[(h5h >> 24) & 0x0f] + + HEX_CHARS[(h5h >> 20) & 0x0f] + HEX_CHARS[(h5h >> 16) & 0x0f] + + HEX_CHARS[(h5h >> 12) & 0x0f] + HEX_CHARS[(h5h >> 8) & 0x0f] + + HEX_CHARS[(h5h >> 4) & 0x0f] + HEX_CHARS[h5h & 0x0f] + + HEX_CHARS[(h5l >> 28) & 0x0f] + HEX_CHARS[(h5l >> 24) & 0x0f] + + HEX_CHARS[(h5l >> 20) & 0x0f] + HEX_CHARS[(h5l >> 16) & 0x0f] + + HEX_CHARS[(h5l >> 12) & 0x0f] + HEX_CHARS[(h5l >> 8) & 0x0f] + + HEX_CHARS[(h5l >> 4) & 0x0f] + HEX_CHARS[h5l & 0x0f]; + } + if (bits === 512) { + hex += + HEX_CHARS[(h6h >> 28) & 0x0f] + HEX_CHARS[(h6h >> 24) & 0x0f] + + HEX_CHARS[(h6h >> 20) & 0x0f] + HEX_CHARS[(h6h >> 16) & 0x0f] + + HEX_CHARS[(h6h >> 12) & 0x0f] + HEX_CHARS[(h6h >> 8) & 0x0f] + + HEX_CHARS[(h6h >> 4) & 0x0f] + HEX_CHARS[h6h & 0x0f] + + HEX_CHARS[(h6l >> 28) & 0x0f] + HEX_CHARS[(h6l >> 24) & 0x0f] + + HEX_CHARS[(h6l >> 20) & 0x0f] + HEX_CHARS[(h6l >> 16) & 0x0f] + + HEX_CHARS[(h6l >> 12) & 0x0f] + HEX_CHARS[(h6l >> 8) & 0x0f] + + HEX_CHARS[(h6l >> 4) & 0x0f] + HEX_CHARS[h6l & 0x0f] + + HEX_CHARS[(h7h >> 28) & 0x0f] + HEX_CHARS[(h7h >> 24) & 0x0f] + + HEX_CHARS[(h7h >> 20) & 0x0f] + HEX_CHARS[(h7h >> 16) & 0x0f] + + HEX_CHARS[(h7h >> 12) & 0x0f] + HEX_CHARS[(h7h >> 8) & 0x0f] + + HEX_CHARS[(h7h >> 4) & 0x0f] + HEX_CHARS[h7h & 0x0f] + + HEX_CHARS[(h7l >> 28) & 0x0f] + HEX_CHARS[(h7l >> 24) & 0x0f] + + HEX_CHARS[(h7l >> 20) & 0x0f] + HEX_CHARS[(h7l >> 16) & 0x0f] + + HEX_CHARS[(h7l >> 12) & 0x0f] + HEX_CHARS[(h7l >> 8) & 0x0f] + + HEX_CHARS[(h7l >> 4) & 0x0f] + HEX_CHARS[h7l & 0x0f]; + } + return hex; + } + + toString(): string { + return this.hex(); + } + + digest(): number[] { + this.finalize(); + const + h0h = this.#h0h, h0l = this.#h0l, h1h = this.#h1h, h1l = this.#h1l, h2h = this.#h2h, h2l = this.#h2l, + h3h = this.#h3h, h3l = this.#h3l, h4h = this.#h4h, h4l = this.#h4l, h5h = this.#h5h, h5l = this.#h5l, + h6h = this.#h6h, h6l = this.#h6l, h7h = this.#h7h, h7l = this.#h7l, bits = this.#bits; + const arr = [ + (h0h >> 24) & 0xff, (h0h >> 16) & 0xff, (h0h >> 8) & 0xff, h0h & 0xff, + (h0l >> 24) & 0xff, (h0l >> 16) & 0xff, (h0l >> 8) & 0xff, h0l & 0xff, + (h1h >> 24) & 0xff, (h1h >> 16) & 0xff, (h1h >> 8) & 0xff, h1h & 0xff, + (h1l >> 24) & 0xff, (h1l >> 16) & 0xff, (h1l >> 8) & 0xff, h1l & 0xff, + (h2h >> 24) & 0xff, (h2h >> 16) & 0xff, (h2h >> 8) & 0xff, h2h & 0xff, + (h2l >> 24) & 0xff, (h2l >> 16) & 0xff, (h2l >> 8) & 0xff, h2l & 0xff, + (h3h >> 24) & 0xff, (h3h >> 16) & 0xff, (h3h >> 8) & 0xff, h3h & 0xff + ]; + if (bits >= 256) { + arr.push((h3l >> 24) & 0xff, (h3l >> 16) & 0xff, (h3l >> 8) & 0xff, h3l & 0xff); + } + if (bits >= 384) { + arr.push( + (h4h >> 24) & 0xff, (h4h >> 16) & 0xff, (h4h >> 8) & 0xff, h4h & 0xff, + (h4l >> 24) & 0xff, (h4l >> 16) & 0xff, (h4l >> 8) & 0xff, h4l & 0xff, + (h5h >> 24) & 0xff, (h5h >> 16) & 0xff, (h5h >> 8) & 0xff, h5h & 0xff, + (h5l >> 24) & 0xff, (h5l >> 16) & 0xff, (h5l >> 8) & 0xff, h5l & 0xff + ); + } + if (bits === 512) { + arr.push( + (h6h >> 24) & 0xff, (h6h >> 16) & 0xff, (h6h >> 8) & 0xff, h6h & 0xff, + (h6l >> 24) & 0xff, (h6l >> 16) & 0xff, (h6l >> 8) & 0xff, h6l & 0xff, + (h7h >> 24) & 0xff, (h7h >> 16) & 0xff, (h7h >> 8) & 0xff, h7h & 0xff, + (h7l >> 24) & 0xff, (h7l >> 16) & 0xff, (h7l >> 8) & 0xff, h7l & 0xff + ); + } + return arr; + } + + array(): number[] { + return this.digest(); + } + + arrayBuffer(): ArrayBuffer { + this.finalize(); + const bits = this.#bits; + const buffer = new ArrayBuffer(bits / 8); + const dataView = new DataView(buffer); + dataView.setUint32(0, this.#h0h); + dataView.setUint32(4, this.#h0l); + dataView.setUint32(8, this.#h1h); + dataView.setUint32(12, this.#h1l); + dataView.setUint32(16, this.#h2h); + dataView.setUint32(20, this.#h2l); + dataView.setUint32(24, this.#h3h); + if (bits >= 256) { + dataView.setUint32(28, this.#h3l); + } + if (bits >= 384) { + dataView.setUint32(32, this.#h4h); + dataView.setUint32(36, this.#h4l); + dataView.setUint32(40, this.#h5h); + dataView.setUint32(44, this.#h5l); + } + if (bits === 512) { + dataView.setUint32(48, this.#h6h); + dataView.setUint32(52, this.#h6l); + dataView.setUint32(56, this.#h7h); + dataView.setUint32(60, this.#h7l); + } + return buffer; + } +} + +export class HmacSha512 extends Sha512 { + #inner: boolean; + #bits: number; + #oKeyPad: number[]; + #sharedMemory: boolean; + + constructor(secretKey: Message, bits = 512, sharedMemory = false) { + super(bits, sharedMemory); + + let key: number[] | Uint8Array; + + if (secretKey instanceof ArrayBuffer) { + key = new Uint8Array(secretKey); + } else if (typeof secretKey === "string") { + const bytes: number[] = []; + const length = secretKey.length; + let index = 0; + let code: number; + for (let i = 0; i < length; ++i) { + code = secretKey.charCodeAt(i); + if (code < 0x80) { + bytes[index++] = code; + } else if (code < 0x800) { + bytes[index++] = 0xc0 | (code >> 6); + bytes[index++] = 0x80 | (code & 0x3f); + } else if (code < 0xd800 || code >= 0xe000) { + bytes[index++] = 0xe0 | (code >> 12); + bytes[index++] = 0x80 | ((code >> 6) & 0x3f); + bytes[index++] = 0x80 | (code & 0x3f); + } else { + code = + 0x10000 + + (((code & 0x3ff) << 10) | (secretKey.charCodeAt(++i) & 0x3ff)); + bytes[index++] = 0xf0 | (code >> 18); + bytes[index++] = 0x80 | ((code >> 12) & 0x3f); + bytes[index++] = 0x80 | ((code >> 6) & 0x3f); + bytes[index++] = 0x80 | (code & 0x3f); + } + } + key = bytes; + } else { + key = secretKey; + } + if (key.length > 128) { + key = new Sha512(bits, true).update(key).array(); + } + const oKeyPad: number[] = []; + const iKeyPad: number[] = []; + for (let i = 0; i < 128; ++i) { + const b = key[i] || 0; + oKeyPad[i] = 0x5c ^ b; + iKeyPad[i] = 0x36 ^ b; + } + this.update(iKeyPad); + this.#inner = true; + this.#bits = bits; + this.#oKeyPad = oKeyPad; + this.#sharedMemory = sharedMemory; + } + + protected finalize(): void { + super.finalize(); + if (this.#inner) { + this.#inner = false; + const innerHash = this.array(); + super.init(this.#bits, this.#sharedMemory); + this.update(this.#oKeyPad); + this.update(innerHash); + super.finalize(); + } + } +} diff --git a/std/hash/sha512_test.ts b/std/hash/sha512_test.ts new file mode 100644 index 00000000000000..d17df32307c8ff --- /dev/null +++ b/std/hash/sha512_test.ts @@ -0,0 +1,408 @@ +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. +import { Sha512, HmacSha512, Message } from "./sha512.ts"; +import { assertEquals } from "../testing/asserts.ts"; +import { join, resolve } from "../path/mod.ts"; + +const { test } = Deno; + +const testdataDir = resolve("hash", "testdata"); + +/** Handy function to convert an array/array buffer to a string of hex values. */ +function toHexString(value: number[] | ArrayBuffer): string { + const array = new Uint8Array(value); + let hex = ""; + for (const v of array) { + const c = v.toString(16); + hex += c.length === 1 ? `0${c}` : c; + } + return hex; +} + +// prettier-ignore +// deno-fmt-ignore +const fixtures: { + sha512bits224: Record>, + sha512bits256: Record>, + sha512: Record>, + hmacSha512bits224: Record>, + hmacSha512bits256: Record>, + hmacSha512: Record> +} = { + sha512bits224: { + "ascii": { + "6ed0dd02806fa89e25de060c19d3ac86cabb87d6a0ddd05c333b84f4": "", + "944cd2847fb54558d4775db0485a50003111c8e5daa63fe722c6aa37": "The quick brown fox jumps over the lazy dog", + "6d6a9279495ec4061769752e7ff9c68b6b0b3c5a281b7917ce0572de": "The quick brown fox jumps over the lazy dog." + }, + "ascii more than 64 bytes": { + "2e962464977b198ee758d615bbc92251ad2e3c0960068e279fd21d2f": "The MD5 message-digest algorithm is a widely used cryptographic hash function producing a 128-bit (16-byte) hash value, typically expressed in text format as a 32 digit hexadecimal number. MD5 has been utilized in a wide variety of cryptographic applications, and is also commonly used to verify data integrity." + }, + "UTF8": { + "0f46a0ae7f226517dd66ece0ce1efa29ffb7ced05ac4566fdcaed188": "中文", + "562f2e4ee7f7451d20dcc6a0ac1a1e1c4a75f09baaf1cf19af3e15f4": "aécio", + "0533318c52b3d4ad355c2a6c7e727ae3d2efa749db480ac33560b059": "𠜎" + }, + "UTF8 more than 64 bytes": { + "f67e191a5d4ee67a272ccaf6cf597f0c4d6a0c46bd631be7cadb0944": "訊息摘要演算法第五版(英語:Message-Digest Algorithm 5,縮寫為MD5),是當前電腦領域用於確保資訊傳輸完整一致而廣泛使用的雜湊演算法之一", + "009c3d1e3172d6df71344982eada855421592aea28acbf660ada7569": "訊息摘要演算法第五版(英語:Message-Digest Algorithm 5,縮寫為MD5),是當前電腦領域用於確保資訊傳輸完整一致而廣泛使用的雜湊演算法之一(又譯雜湊演算法、摘要演算法等),主流程式語言普遍已有MD5的實作。" + }, + "special length": { + "6fe6ce0f03b9cd09851e05ba5e3103df56d2a3dbb379fee437e1cdd3": "0123456780123456780123456780123456780123456780123456780", + "9e6994d879f14c242dea25ebc4d03ae6fc710f5eb60c3962b9dba797": "01234567801234567801234567801234567801234567801234567801", + "204ce3b2af187fe90494cb3e4517257c44917bb7ea6578264baa4fcf": "0123456780123456780123456780123456780123456780123456780123456780", + "69ce912fd1f87e02601d6153c02769ebd7c42b29dcb7963a1c3996da": "01234567801234567801234567801234567801234567801234567801234567801234567", + "bd98be1f148dddd8a98c6ba31628c354456b9754166738fe1aba1037": "012345678012345678012345678012345678012345678012345678012345678012345678" + }, + "Array": { + "6ed0dd02806fa89e25de060c19d3ac86cabb87d6a0ddd05c333b84f4": [], + "6945cf025ed66055282665c546781e32c5a479b5e9b479e96b0c23fe": [211, 212], + "944cd2847fb54558d4775db0485a50003111c8e5daa63fe722c6aa37": [84, 104, 101, 32, 113, 117, 105, 99, 107, 32, 98, 114, 111, 119, 110, 32, 102, 111, 120, 32, 106, 117, 109, 112, 115, 32, 111, 118, 101, 114, 32, 116, 104, 101, 32, 108, 97, 122, 121, 32, 100, 111, 103], + "69ce912fd1f87e02601d6153c02769ebd7c42b29dcb7963a1c3996da": [48, 49, 50, 51, 52, 53, 54, 55, 56, 48, 49, 50, 51, 52, 53, 54, 55, 56, 48, 49, 50, 51, 52, 53, 54, 55, 56, 48, 49, 50, 51, 52, 53, 54, 55, 56, 48, 49, 50, 51, 52, 53, 54, 55, 56, 48, 49, 50, 51, 52, 53, 54, 55, 56, 48, 49, 50, 51, 52, 53, 54, 55, 56, 48, 49, 50, 51, 52, 53, 54, 55] + }, + "Uint8Array": { + "6945cf025ed66055282665c546781e32c5a479b5e9b479e96b0c23fe": new Uint8Array([211, 212]), + "944cd2847fb54558d4775db0485a50003111c8e5daa63fe722c6aa37": new Uint8Array([84, 104, 101, 32, 113, 117, 105, 99, 107, 32, 98, 114, 111, 119, 110, 32, 102, 111, 120, 32, 106, 117, 109, 112, 115, 32, 111, 118, 101, 114, 32, 116, 104, 101, 32, 108, 97, 122, 121, 32, 100, 111, 103]) + }, + "Int8Array": { + "944cd2847fb54558d4775db0485a50003111c8e5daa63fe722c6aa37": new Int8Array([84, 104, 101, 32, 113, 117, 105, 99, 107, 32, 98, 114, 111, 119, 110, 32, 102, 111, 120, 32, 106, 117, 109, 112, 115, 32, 111, 118, 101, 114, 32, 116, 104, 101, 32, 108, 97, 122, 121, 32, 100, 111, 103]) + }, + "ArrayBuffer": { + "6ed0dd02806fa89e25de060c19d3ac86cabb87d6a0ddd05c333b84f4": new ArrayBuffer(0), + "283bb59af7081ed08197227d8f65b9591ffe1155be43e9550e57f941": new ArrayBuffer(1) + } + }, + sha512bits256: { + "ascii": { + "c672b8d1ef56ed28ab87c3622c5114069bdd3ad7b8f9737498d0c01ecef0967a": "", + "dd9d67b371519c339ed8dbd25af90e976a1eeefd4ad3d889005e532fc5bef04d": "The quick brown fox jumps over the lazy dog", + "1546741840f8a492b959d9b8b2344b9b0eb51b004bba35c0aebaac86d45264c3": "The quick brown fox jumps over the lazy dog." + }, + "ascii more than 64 bytes": { + "21e2e940930b23f1de6377086d07e22033c6bbf3fd9fbf4b62ec66e6c08c25be": "The MD5 message-digest algorithm is a widely used cryptographic hash function producing a 128-bit (16-byte) hash value, typically expressed in text format as a 32 digit hexadecimal number. MD5 has been utilized in a wide variety of cryptographic applications, and is also commonly used to verify data integrity." + }, + "UTF8": { + "b6dab29c16ec35ab34a5d92ff135b58de96741dda78b1009a2181cf8b45d2f72": "中文", + "122802ca08e39c2ef46f6a81379dc5683bd8aa074dfb54259f0add4d8b5504bc": "aécio", + "1032308151c0f4f5f8d4e0d96956352eb8ff87da98df8878d8795a858a7e7c08": "𠜎" + }, + "UTF8 more than 64 bytes": { + "d32a41d9858e45b68402f77cf9f3c3f992c36a4bffd230f78d666c87f97eaf7e": "訊息摘要演算法第五版(英語:Message-Digest Algorithm 5,縮寫為MD5),是當前電腦領域用於確保資訊傳輸完整一致而廣泛使用的雜湊演算法之一", + "bd1abad59e6b8ad69bc17b6e05aa13f0cb725467fbeb45b83d3e4094332d1367": "訊息摘要演算法第五版(英語:Message-Digest Algorithm 5,縮寫為MD5),是當前電腦領域用於確保資訊傳輸完整一致而廣泛使用的雜湊演算法之一(又譯雜湊演算法、摘要演算法等),主流程式語言普遍已有MD5的實作。" + }, + "special length": { + "99fb09c8564fbd52274cfaf1130ae02dad89efac9a31dc00e9bfc13db1ff4f56": "0123456780123456780123456780123456780123456780123456780", + "7a3204b58878f5a65a54f77e270d5df579a8016e0e472cc91833689c4cf8ca07": "01234567801234567801234567801234567801234567801234567801", + "f4aa5f7692e6fee7237510b9a886f7b7aa4098926b45eaf70672bdd6d316a633": "0123456780123456780123456780123456780123456780123456780123456780", + "3f8fc8ec35656592ce61bf44895b6d94077aae3bddd99236a0b04ccf936699ed": "01234567801234567801234567801234567801234567801234567801234567801234567", + "4cb330a62170d92fe3d03bcf9284b590cf08d38d3a3c1e661abba3641d0b7502": "012345678012345678012345678012345678012345678012345678012345678012345678" + }, + "Array": { + "c672b8d1ef56ed28ab87c3622c5114069bdd3ad7b8f9737498d0c01ecef0967a": [], + "547cf572033bb67ae341d010b348691ee9c550d07b796e0c6e6ad3503fa36cb3": [211, 212], + "dd9d67b371519c339ed8dbd25af90e976a1eeefd4ad3d889005e532fc5bef04d": [84, 104, 101, 32, 113, 117, 105, 99, 107, 32, 98, 114, 111, 119, 110, 32, 102, 111, 120, 32, 106, 117, 109, 112, 115, 32, 111, 118, 101, 114, 32, 116, 104, 101, 32, 108, 97, 122, 121, 32, 100, 111, 103], + "3f8fc8ec35656592ce61bf44895b6d94077aae3bddd99236a0b04ccf936699ed": [48, 49, 50, 51, 52, 53, 54, 55, 56, 48, 49, 50, 51, 52, 53, 54, 55, 56, 48, 49, 50, 51, 52, 53, 54, 55, 56, 48, 49, 50, 51, 52, 53, 54, 55, 56, 48, 49, 50, 51, 52, 53, 54, 55, 56, 48, 49, 50, 51, 52, 53, 54, 55, 56, 48, 49, 50, 51, 52, 53, 54, 55, 56, 48, 49, 50, 51, 52, 53, 54, 55] + }, + "Uint8Array": { + "547cf572033bb67ae341d010b348691ee9c550d07b796e0c6e6ad3503fa36cb3": new Uint8Array([211, 212]), + "dd9d67b371519c339ed8dbd25af90e976a1eeefd4ad3d889005e532fc5bef04d": new Uint8Array([84, 104, 101, 32, 113, 117, 105, 99, 107, 32, 98, 114, 111, 119, 110, 32, 102, 111, 120, 32, 106, 117, 109, 112, 115, 32, 111, 118, 101, 114, 32, 116, 104, 101, 32, 108, 97, 122, 121, 32, 100, 111, 103]) + }, + "Int8Array": { + "dd9d67b371519c339ed8dbd25af90e976a1eeefd4ad3d889005e532fc5bef04d": new Int8Array([84, 104, 101, 32, 113, 117, 105, 99, 107, 32, 98, 114, 111, 119, 110, 32, 102, 111, 120, 32, 106, 117, 109, 112, 115, 32, 111, 118, 101, 114, 32, 116, 104, 101, 32, 108, 97, 122, 121, 32, 100, 111, 103]) + }, + "ArrayBuffer": { + "c672b8d1ef56ed28ab87c3622c5114069bdd3ad7b8f9737498d0c01ecef0967a": new ArrayBuffer(0), + "10baad1713566ac2333467bddb0597dec9066120dd72ac2dcb8394221dcbe43d": new ArrayBuffer(1) + } + }, + sha512: { + "ascii": { + "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e": "", + "07e547d9586f6a73f73fbac0435ed76951218fb7d0c8d788a309d785436bbb642e93a252a954f23912547d1e8a3b5ed6e1bfd7097821233fa0538f3db854fee6": "The quick brown fox jumps over the lazy dog", + "91ea1245f20d46ae9a037a989f54f1f790f0a47607eeb8a14d12890cea77a1bbc6c7ed9cf205e67b7f2b8fd4c7dfd3a7a8617e45f3c463d481c7e586c39ac1ed": "The quick brown fox jumps over the lazy dog." + }, + "ascii more than 64 bytes": { + "a8dedff31e3be9df6413ef5b4ecb93d62d3fbcb04297552eab5370e04afd45927854a4373037e81a50186e678d818c9ba824f4c850f3d0f02764af0252076979": "The MD5 message-digest algorithm is a widely used cryptographic hash function producing a 128-bit (16-byte) hash value, typically expressed in text format as a 32 digit hexadecimal number. MD5 has been utilized in a wide variety of cryptographic applications, and is also commonly used to verify data integrity." + }, + "UTF8": { + "8b88efc2ebbcbdad5ac2d65af05bec57bda25e71fd5fb25bbd892057a2755fbd05d8d8491cb2946febd5b0f124ffdfbaecf7e34946353c4f1b5ab29545895468": "中文", + "e1c6925243db76985abacaf9fa85e22697f549e67f65a36c88e4046a2260990ff9eefc3402396ea8dcbe8c592d8d5671bea612156eda38d3708d394bbd17d493": "aécio", + "f3e7ee9cdf7dbb52f7edd59ce3d49868c64f2b3aceceab060b8eaaebdf9de0dae5866d660e3319c5aad426a2176cb1703efc73eb24d1a90458ceda1b7f4e3940": "𠜎" + }, + "UTF8 more than 64 bytes": { + "6cb7f6d3381a187edadb43c7cdcfbbed4d2c213a7dce8ea08fe42b9882b64e643202b4974a6db94f94650ab9173d97c58bd59f6d19d27e01aab76d8d08855c65": "訊息摘要演算法第五版(英語:Message-Digest Algorithm 5,縮寫為MD5),是當前電腦領域用於確保資訊傳輸完整一致而廣泛使用的雜湊演算法之一", + "d24af1901aaf1458f089a6eddf784ce61c3012aee0df98bdb67ad2dc6b41a3b4051d40caac524373930ae396a2dde99a9204871b40892eea3e5f3c8d46da0c3c": "訊息摘要演算法第五版(英語:Message-Digest Algorithm 5,縮寫為MD5),是當前電腦領域用於確保資訊傳輸完整一致而廣泛使用的雜湊演算法之一(又譯雜湊演算法、摘要演算法等),主流程式語言普遍已有MD5的實作。" + }, + "special length": { + "6b4a72eb22d2d24c0a429dd99ce5835b134144ac5fce446f66dbf2f421dcc5f8a177e4774f4a48173c5640724b186c2c4112a80937b1167f3e7bb511f4c41b6a": "0123456780123456780123456780123456780123456780123456780", + "76f3cb2ed5b0b405479495b2d3576f4b469b6ffc4b06e3b512a658b84c1b91cf72c41c54d8714ecf19d04696f09e0034632fe98ae848ffd35b83c7e72399a590": "01234567801234567801234567801234567801234567801234567801", + "56d2391faebd8d69b067cd5c0cb364ffc2e2ab87ce5bb06a562b44c8dcb0b83816ad2c0c062537838992b181fadc43ff00e1ebb92ddb1129b81b4864bafb5f63": "0123456780123456780123456780123456780123456780123456780123456780", + "317ab88f192258711b8ae0197395b7a8191796fb41140c16c596699481149b47130e26b3bfa724227202fa8371752ca92e3cb9dd202caf29334038e0848cb43f": "01234567801234567801234567801234567801234567801234567801234567801234567", + "23880e96199df52b4386d190adddaa33cbf7e0bfa7d2067c60eb44ee103667fd002c32e184195fef65fd4178853b1c661d9f260d721df85872e5f645f4388841": "012345678012345678012345678012345678012345678012345678012345678012345678" + }, + "Array": { + "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e": [], + "8df0195b2807fdc8c7674c191562e9d0db38b257cc0d3df64669878fe5bb1bbaff53cc8898edcf46cbecb945dc71b6ad738da8ca6f3a824123a54afde5d1d5b0": [211, 212], + "07e547d9586f6a73f73fbac0435ed76951218fb7d0c8d788a309d785436bbb642e93a252a954f23912547d1e8a3b5ed6e1bfd7097821233fa0538f3db854fee6": [84, 104, 101, 32, 113, 117, 105, 99, 107, 32, 98, 114, 111, 119, 110, 32, 102, 111, 120, 32, 106, 117, 109, 112, 115, 32, 111, 118, 101, 114, 32, 116, 104, 101, 32, 108, 97, 122, 121, 32, 100, 111, 103], + "317ab88f192258711b8ae0197395b7a8191796fb41140c16c596699481149b47130e26b3bfa724227202fa8371752ca92e3cb9dd202caf29334038e0848cb43f": [48, 49, 50, 51, 52, 53, 54, 55, 56, 48, 49, 50, 51, 52, 53, 54, 55, 56, 48, 49, 50, 51, 52, 53, 54, 55, 56, 48, 49, 50, 51, 52, 53, 54, 55, 56, 48, 49, 50, 51, 52, 53, 54, 55, 56, 48, 49, 50, 51, 52, 53, 54, 55, 56, 48, 49, 50, 51, 52, 53, 54, 55, 56, 48, 49, 50, 51, 52, 53, 54, 55] + }, + "Uint8Array": { + "8df0195b2807fdc8c7674c191562e9d0db38b257cc0d3df64669878fe5bb1bbaff53cc8898edcf46cbecb945dc71b6ad738da8ca6f3a824123a54afde5d1d5b0": new Uint8Array([211, 212]), + "07e547d9586f6a73f73fbac0435ed76951218fb7d0c8d788a309d785436bbb642e93a252a954f23912547d1e8a3b5ed6e1bfd7097821233fa0538f3db854fee6": new Uint8Array([84, 104, 101, 32, 113, 117, 105, 99, 107, 32, 98, 114, 111, 119, 110, 32, 102, 111, 120, 32, 106, 117, 109, 112, 115, 32, 111, 118, 101, 114, 32, 116, 104, 101, 32, 108, 97, 122, 121, 32, 100, 111, 103]) + }, + "Int8Array": { + "07e547d9586f6a73f73fbac0435ed76951218fb7d0c8d788a309d785436bbb642e93a252a954f23912547d1e8a3b5ed6e1bfd7097821233fa0538f3db854fee6": new Int8Array([84, 104, 101, 32, 113, 117, 105, 99, 107, 32, 98, 114, 111, 119, 110, 32, 102, 111, 120, 32, 106, 117, 109, 112, 115, 32, 111, 118, 101, 114, 32, 116, 104, 101, 32, 108, 97, 122, 121, 32, 100, 111, 103]) + }, + "ArrayBuffer": { + "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e": new ArrayBuffer(0), + "b8244d028981d693af7b456af8efa4cad63d282e19ff14942c246e50d9351d22704a802a71c3580b6370de4ceb293c324a8423342557d4e5c38438f0e36910ee": new ArrayBuffer(1) + } + }, + hmacSha512bits224: { + "Test Vectors": { + "b244ba01307c0e7a8ccaad13b1067a4cf6b961fe0c6a20bda3d92039": [ + [0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b], + "Hi There" + ], + "4a530b31a79ebcce36916546317c45f247d83241dfb818fd37254bde": [ + "Jefe", + "what do ya want for nothing?" + ], + "db34ea525c2c216ee5a6ccb6608bea870bbef12fd9b96a5109e2b6fc": [ + [0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa], + [0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd] + ], + "c2391863cda465c6828af06ac5d4b72d0b792109952da530e11a0d26": [ + [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19], + [0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd] + ], + "29bef8ce88b54d4226c3c7718ea9e32ace2429026f089e38cea9aeda": [ + [0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa], + "Test Using Larger Than Block-Size Key - Hash Key First" + ], + "82a9619b47af0cea73a8b9741355ce902d807ad87ee9078522a246e1": [ + [0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa], + "This is a test using a larger than block-size key and a larger than block-size data. The key needs to be hashed before being used by the HMAC algorithm." + ] + }, + "UTF8": { + "24e1153464bf5ec62ad2eeeb88ff644f2441a124d1e16e8ae5fb1508": ["中文", "aécio"], + "7a08cecb4700304bc5c466acc1fb312d198374817052a03df07610c6": ["aécio", "𠜎"], + "697973678b7d0075676ec3cbbc19e343ed16fa20c14d8074b76b0861": ["𠜎", "中文"] + }, + "Uint8Array": { + "defdc4a1a6597147ea0c7d0a59ae0a5e64b9413a6400acac28aecdd1": [new Uint8Array(0), "Hi There"] + }, + "ArrayBuffer": { + "defdc4a1a6597147ea0c7d0a59ae0a5e64b9413a6400acac28aecdd1": [new ArrayBuffer(0), "Hi There"] + } + }, + hmacSha512bits256: { + "Test Vectors": { + "9f9126c3d9c3c330d760425ca8a217e31feae31bfe70196ff81642b868402eab": [ + [0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b], + "Hi There" + ], + "6df7b24630d5ccb2ee335407081a87188c221489768fa2020513b2d593359456": [ + "Jefe", + "what do ya want for nothing?" + ], + "229006391d66c8ecddf43ba5cf8f83530ef221a4e9401840d1bead5137c8a2ea": [ + [0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa], + [0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd] + ], + "36d60c8aa1d0be856e10804cf836e821e8733cbafeae87630589fd0b9b0a2f4c": [ + [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19], + [0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd] + ], + "87123c45f7c537a404f8f47cdbedda1fc9bec60eeb971982ce7ef10e774e6539": [ + [0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa], + "Test Using Larger Than Block-Size Key - Hash Key First" + ], + "6ea83f8e7315072c0bdaa33b93a26fc1659974637a9db8a887d06c05a7f35a66": [ + [0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa], + "This is a test using a larger than block-size key and a larger than block-size data. The key needs to be hashed before being used by the HMAC algorithm." + ] + }, + "UTF8": { + "633400fa4bc12c3690efa218c90b56ab1af81b91ad62b57bdbe84988c51071e0": ["中文", "aécio"], + "80eff00e32e0c0813d4c04e296b5ac079ec896e673cc04b0ff14222e151ad0b0": ["aécio", "𠜎"], + "3f801c729e5330a0b91aecc751a26c35688a94989e2098c73bf0c6ac02b99e58": ["𠜎", "中文"] + }, + "Uint8Array": { + "1e08e33f9357abd2a3cfbc82a623d892bb6dccf175d22c0cf24269a7a59dfad6": [new Uint8Array(0), "Hi There"] + }, + "ArrayBuffer": { + "1e08e33f9357abd2a3cfbc82a623d892bb6dccf175d22c0cf24269a7a59dfad6": [new ArrayBuffer(0), "Hi There"] + } + }, + hmacSha512: { + "Test Vectors": { + "87aa7cdea5ef619d4ff0b4241a1d6cb02379f4e2ce4ec2787ad0b30545e17cdedaa833b7d6b8a702038b274eaea3f4e4be9d914eeb61f1702e696c203a126854": [ + [0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b], + "Hi There" + ], + "164b7a7bfcf819e2e395fbe73b56e0a387bd64222e831fd610270cd7ea2505549758bf75c05a994a6d034f65f8f0e6fdcaeab1a34d4a6b4b636e070a38bce737": [ + "Jefe", + "what do ya want for nothing?" + ], + "fa73b0089d56a284efb0f0756c890be9b1b5dbdd8ee81a3655f83e33b2279d39bf3e848279a722c806b485a47e67c807b946a337bee8942674278859e13292fb": [ + [0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa], + [0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd] + ], + "b0ba465637458c6990e5a8c5f61d4af7e576d97ff94b872de76f8050361ee3dba91ca5c11aa25eb4d679275cc5788063a5f19741120c4f2de2adebeb10a298dd": [ + [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19], + [0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd] + ], + "80b24263c7c1a3ebb71493c1dd7be8b49b46d1f41b4aeec1121b013783f8f3526b56d037e05f2598bd0fd2215d6a1e5295e64f73f63f0aec8b915a985d786598": [ + [0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa], + "Test Using Larger Than Block-Size Key - Hash Key First" + ], + "e37b6a775dc87dbaa4dfa9f96e5e3ffddebd71f8867289865df5a32d20cdc944b6022cac3c4982b10d5eeb55c3e4de15134676fb6de0446065c97440fa8c6a58": [ + [0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa], + "This is a test using a larger than block-size key and a larger than block-size data. The key needs to be hashed before being used by the HMAC algorithm." + ] + }, + "UTF8": { + "e9e5906be0aecbc028a5fc759c9dbb86efc9a22950af8e678302a215aeee0b021edc50bbdd71c656730177b7e96c9a3bcf3cb9592bc84a5f3e8900cb67c7eca6": ["中文", "aécio"], + "d02a8d258d855967d5be47240bbedd986a31c29eb5beb35abdbe2725651bf33a195cdfaadb9e76dc4790c71dfea33f708afa04b9471d03f5f0db8440993b9612": ["aécio", "𠜎"], + "a443d463546586a5dd591ef848f0939c3a7089d63ef81d58ccc0a2611a1d374a39717d6893ea10d61ca0e87d5be7c80b29b2ed991c4a62e12d10c7f6b1b9d7ae": ["𠜎", "中文"] + }, + "Uint8Array": { + "f7688a104326d36c1940f6d28d746c0661d383e0d14fe8a04649444777610f5dd9565a36846ab9e9e734cf380d3a070d8ef021b5f3a50c481710a464968e3419": [new Uint8Array(0), "Hi There"] + }, + "ArrayBuffer": { + "f7688a104326d36c1940f6d28d746c0661d383e0d14fe8a04649444777610f5dd9565a36846ab9e9e734cf380d3a070d8ef021b5f3a50c481710a464968e3419": [new ArrayBuffer(0), "Hi There"] + } + }, +}; + +const methods = ["array", "arrayBuffer", "digest", "hex"] as const; + +for (const method of methods) { + for (const [name, tests] of Object.entries(fixtures.sha512bits224)) { + let i = 1; + for (const [expected, message] of Object.entries(tests)) { + test({ + name: `sha512/224.${method}() - ${name} - #${i++}`, + fn() { + const algorithm = new Sha512(224); + algorithm.update(message); + const actual = + method === "hex" + ? algorithm[method]() + : toHexString(algorithm[method]()); + assertEquals(actual, expected); + }, + }); + } + } +} + +for (const method of methods) { + for (const [name, tests] of Object.entries(fixtures.sha512bits256)) { + let i = 1; + for (const [expected, message] of Object.entries(tests)) { + test({ + name: `sha512/256.${method}() - ${name} - #${i++}`, + fn() { + const algorithm = new Sha512(256); + algorithm.update(message); + const actual = + method === "hex" + ? algorithm[method]() + : toHexString(algorithm[method]()); + assertEquals(actual, expected); + }, + }); + } + } +} + +for (const method of methods) { + for (const [name, tests] of Object.entries(fixtures.sha512)) { + let i = 1; + for (const [expected, message] of Object.entries(tests)) { + test({ + name: `sha512.${method}() - ${name} - #${i++}`, + fn() { + const algorithm = new Sha512(); + algorithm.update(message); + const actual = + method === "hex" + ? algorithm[method]() + : toHexString(algorithm[method]()); + assertEquals(actual, expected); + }, + }); + } + } +} + +for (const method of methods) { + for (const [name, tests] of Object.entries(fixtures.hmacSha512bits224)) { + let i = 1; + for (const [expected, [key, message]] of Object.entries(tests)) { + test({ + name: `hmacSha512/224.${method}() - ${name} - #${i++}`, + fn() { + const algorithm = new HmacSha512(key, 224); + algorithm.update(message); + const actual = + method === "hex" + ? algorithm[method]() + : toHexString(algorithm[method]()); + assertEquals(actual, expected); + }, + }); + } + } +} + +for (const method of methods) { + for (const [name, tests] of Object.entries(fixtures.hmacSha512bits256)) { + let i = 1; + for (const [expected, [key, message]] of Object.entries(tests)) { + test({ + name: `hmacSha512/256.${method}() - ${name} - #${i++}`, + fn() { + const algorithm = new HmacSha512(key, 256); + algorithm.update(message); + const actual = + method === "hex" + ? algorithm[method]() + : toHexString(algorithm[method]()); + assertEquals(actual, expected); + }, + }); + } + } +} + +for (const method of methods) { + for (const [name, tests] of Object.entries(fixtures.hmacSha512)) { + let i = 1; + for (const [expected, [key, message]] of Object.entries(tests)) { + test({ + name: `hmacSha512.${method}() - ${name} - #${i++}`, + fn() { + const algorithm = new HmacSha512(key); + algorithm.update(message); + const actual = + method === "hex" + ? algorithm[method]() + : toHexString(algorithm[method]()); + assertEquals(actual, expected); + }, + }); + } + } +} + +test("[hash/sha512] test Uint8Array from Reader", async () => { + const data = await Deno.readFile(join(testdataDir, "hashtest")); + const hash = new Sha512().update(data).hex(); + assertEquals( + hash, + "ee26b0dd4af7e749aa1a8ee3c10ae9923f618980772e473f8819a5d4940e0db27ac185f8a0e1d5f84f88bc887fd67b143732c304cc5fa9ad8e6f57f50028a8ff" + ); +}); diff --git a/std/http/_io.ts b/std/http/_io.ts index 631adafd01f6b4..82954cceecfe88 100644 --- a/std/http/_io.ts +++ b/std/http/_io.ts @@ -113,50 +113,60 @@ export function chunkedBodyReader(h: Headers, r: BufReader): Deno.Reader { return { read }; } -const kProhibitedTrailerHeaders = [ - "transfer-encoding", - "content-length", - "trailer", -]; +function isProhibidedForTrailer(key: string): boolean { + const s = new Set(["transfer-encoding", "content-length", "trailer"]); + return s.has(key.toLowerCase()); +} -/** - * Read trailer headers from reader and append values to headers. - * "trailer" field will be deleted. - * */ +/** Read trailer headers from reader and append values to headers. "trailer" + * field will be deleted. */ export async function readTrailers( headers: Headers, r: BufReader ): Promise { - const keys = parseTrailer(headers.get("trailer")); - if (!keys) return; + const trailers = parseTrailer(headers.get("trailer")); + if (trailers == null) return; + const trailerNames = [...trailers.keys()]; const tp = new TextProtoReader(r); const result = await tp.readMIMEHeader(); - assert(result !== null, "trailer must be set"); + if (result == null) { + throw new Deno.errors.InvalidData("Missing trailer header."); + } + const undeclared = [...result.keys()].filter( + (k) => !trailerNames.includes(k) + ); + if (undeclared.length > 0) { + throw new Deno.errors.InvalidData( + `Undeclared trailers: ${Deno.inspect(undeclared)}.` + ); + } for (const [k, v] of result) { - if (!keys.has(k)) { - throw new Error("Undeclared trailer field"); - } - keys.delete(k); headers.append(k, v); } - assert(keys.size === 0, "Missing trailers"); + const missingTrailers = trailerNames.filter((k) => !result.has(k)); + if (missingTrailers.length > 0) { + throw new Deno.errors.InvalidData( + `Missing trailers: ${Deno.inspect(missingTrailers)}.` + ); + } headers.delete("trailer"); } -function parseTrailer(field: string | null): Set | undefined { +function parseTrailer(field: string | null): Headers | undefined { if (field == null) { return undefined; } - const keys = field.split(",").map((v) => v.trim()); - if (keys.length === 0) { - throw new Error("Empty trailer"); + const trailerNames = field.split(",").map((v) => v.trim().toLowerCase()); + if (trailerNames.length === 0) { + throw new Deno.errors.InvalidData("Empty trailer header."); } - for (const invalid of kProhibitedTrailerHeaders) { - if (keys.includes(invalid)) { - throw new Error(`Prohibited field for trailer`); - } + const prohibited = trailerNames.filter((k) => isProhibidedForTrailer(k)); + if (prohibited.length > 0) { + throw new Deno.errors.InvalidData( + `Prohibited trailer names: ${Deno.inspect(prohibited)}.` + ); } - return new Set(keys); + return new Headers(trailerNames.map((key) => [key, ""])); } export async function writeChunkedBody( @@ -177,7 +187,8 @@ export async function writeChunkedBody( await writer.write(endChunk); } -/** write trailer headers to writer. it mostly should be called after writeResponse */ +/** Write trailer headers to writer. It should mostly should be called after + * `writeResponse()`. */ export async function writeTrailers( w: Deno.Writer, headers: Headers, @@ -185,29 +196,31 @@ export async function writeTrailers( ): Promise { const trailer = headers.get("trailer"); if (trailer === null) { - throw new Error('response headers must have "trailer" header field'); + throw new TypeError("Missing trailer header."); } const transferEncoding = headers.get("transfer-encoding"); if (transferEncoding === null || !transferEncoding.match(/^chunked/)) { - throw new Error( - `trailer headers is only allowed for "transfer-encoding: chunked": got "${transferEncoding}"` + throw new TypeError( + `Trailers are only allowed for "transfer-encoding: chunked", got "transfer-encoding: ${transferEncoding}".` ); } const writer = BufWriter.create(w); - const trailerHeaderFields = trailer - .split(",") - .map((s) => s.trim().toLowerCase()); - for (const f of trailerHeaderFields) { - assert( - !kProhibitedTrailerHeaders.includes(f), - `"${f}" is prohibited for trailer header` + const trailerNames = trailer.split(",").map((s) => s.trim().toLowerCase()); + const prohibitedTrailers = trailerNames.filter((k) => + isProhibidedForTrailer(k) + ); + if (prohibitedTrailers.length > 0) { + throw new TypeError( + `Prohibited trailer names: ${Deno.inspect(prohibitedTrailers)}.` ); } + const undeclared = [...trailers.keys()].filter( + (k) => !trailerNames.includes(k) + ); + if (undeclared.length > 0) { + throw new TypeError(`Undeclared trailers: ${Deno.inspect(undeclared)}.`); + } for (const [key, value] of trailers) { - assert( - trailerHeaderFields.includes(key), - `Not trailer header field: ${key}` - ); await writer.write(encoder.encode(`${key}: ${value}\r\n`)); } await writer.write(encoder.encode("\r\n")); diff --git a/std/http/_io_test.ts b/std/http/_io_test.ts index c22ebdf07e4d74..3b385d013d3b0c 100644 --- a/std/http/_io_test.ts +++ b/std/http/_io_test.ts @@ -1,5 +1,4 @@ import { - AssertionError, assertThrowsAsync, assertEquals, assert, @@ -82,7 +81,7 @@ test("chunkedBodyReader with trailers", async () => { test("readTrailers", async () => { const h = new Headers({ - trailer: "deno,node", + trailer: "Deno, Node", }); const trailer = ["deno: land", "node: js", "", ""].join("\r\n"); await readTrailers(h, new BufReader(new Buffer(encode(trailer)))); @@ -105,14 +104,14 @@ test("readTrailer should throw if undeclared headers found in trailer", async () async () => { await readTrailers(h, new BufReader(new Buffer(encode(trailer)))); }, - Error, - "Undeclared trailer field" + Deno.errors.InvalidData, + `Undeclared trailers: [ "` ); } }); test("readTrailer should throw if trailer contains prohibited fields", async () => { - for (const f of ["content-length", "trailer", "transfer-encoding"]) { + for (const f of ["Content-Length", "Trailer", "Transfer-Encoding"]) { const h = new Headers({ trailer: f, }); @@ -120,8 +119,8 @@ test("readTrailer should throw if trailer contains prohibited fields", async () async () => { await readTrailers(h, new BufReader(new Buffer())); }, - Error, - "Prohibited field for trailer" + Deno.errors.InvalidData, + `Prohibited trailer names: [ "` ); } }); @@ -145,15 +144,15 @@ test("writeTrailer should throw", async () => { () => { return writeTrailers(w, new Headers(), new Headers()); }, - Error, - 'must have "trailer"' + TypeError, + "Missing trailer header." ); await assertThrowsAsync( () => { return writeTrailers(w, new Headers({ trailer: "deno" }), new Headers()); }, - Error, - "only allowed" + TypeError, + `Trailers are only allowed for "transfer-encoding: chunked", got "transfer-encoding: null".` ); for (const f of ["content-length", "trailer", "transfer-encoding"]) { await assertThrowsAsync( @@ -164,8 +163,8 @@ test("writeTrailer should throw", async () => { new Headers({ [f]: "1" }) ); }, - AssertionError, - "prohibited" + TypeError, + `Prohibited trailer names: [ "` ); } await assertThrowsAsync( @@ -176,8 +175,8 @@ test("writeTrailer should throw", async () => { new Headers({ node: "js" }) ); }, - AssertionError, - "Not trailer" + TypeError, + `Undeclared trailers: [ "node" ].` ); }); diff --git a/std/http/file_server.ts b/std/http/file_server.ts index 81515b04b832f7..7614c3ab2f5125 100755 --- a/std/http/file_server.ts +++ b/std/http/file_server.ts @@ -1,4 +1,4 @@ -#!/usr/bin/env -S deno --allow-net +#!/usr/bin/env -S deno run --allow-net --allow-read // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. // This program serves files in the current directory over HTTP. @@ -34,10 +34,7 @@ interface FileServerArgs { const encoder = new TextEncoder(); const serverArgs = parse(args) as FileServerArgs; - -const CORSEnabled = serverArgs.cors ? true : false; const target = posix.resolve(serverArgs._[0] ?? ""); -const addr = `0.0.0.0:${serverArgs.port ?? serverArgs.p ?? 4507}`; const MEDIA_TYPES: Record = { ".md": "text/markdown", @@ -52,6 +49,7 @@ const MEDIA_TYPES: Record = { ".jsx": "text/jsx", ".gz": "application/gzip", ".css": "text/css", + ".wasm": "application/wasm", }; /** Returns the content-type based on the extension of a path. */ @@ -59,23 +57,6 @@ function contentType(path: string): string | undefined { return MEDIA_TYPES[extname(path)]; } -if (serverArgs.h ?? serverArgs.help) { - console.log(`Deno File Server - Serves a local directory in HTTP. - -INSTALL: - deno install --allow-net --allow-read https://deno.land/std/http/file_server.ts - -USAGE: - file_server [path] [options] - -OPTIONS: - -h, --help Prints help information - -p, --port Set port - --cors Enable CORS via the "Access-Control-Allow-Origin" header`); - exit(); -} - function modeToString(isDir: boolean, maybeMode: number | null): string { const modeMap = ["---", "--x", "-w-", "-wx", "r--", "r-x", "rw-", "rwx"]; @@ -126,6 +107,9 @@ export async function serveFile( if (contentTypeValue) { headers.set("content-type", contentTypeValue); } + req.done.then(() => { + file.close(); + }); return { status: 200, body: file, @@ -315,6 +299,26 @@ function html(strings: TemplateStringsArray, ...values: unknown[]): string { } function main(): void { + const CORSEnabled = serverArgs.cors ? true : false; + const addr = `0.0.0.0:${serverArgs.port ?? serverArgs.p ?? 4507}`; + + if (serverArgs.h ?? serverArgs.help) { + console.log(`Deno File Server + Serves a local directory in HTTP. + + INSTALL: + deno install --allow-net --allow-read https://deno.land/std/http/file_server.ts + + USAGE: + file_server [path] [options] + + OPTIONS: + -h, --help Prints help information + -p, --port Set port + --cors Enable CORS via the "Access-Control-Allow-Origin" header`); + exit(); + } + listenAndServe( addr, async (req): Promise => { diff --git a/std/http/file_server_test.ts b/std/http/file_server_test.ts index 97c07c2503913c..dbbaf81ff799b7 100644 --- a/std/http/file_server_test.ts +++ b/std/http/file_server_test.ts @@ -38,6 +38,24 @@ async function startFileServer({ assert(s !== null && s.includes("server listening")); } +async function startFileServerAsLibrary({}: FileServerCfg = {}): Promise { + fileServer = await Deno.run({ + cmd: [ + Deno.execPath(), + "run", + "--allow-read", + "--allow-net", + "http/testdata/file_server_as_library.ts", + ], + stdout: "piped", + stderr: "null", + }); + assert(fileServer.stdout != null); + const r = new TextProtoReader(new BufReader(fileServer.stdout)); + const s = await r.readLine(); + assert(s !== null && s.includes("Server running...")); +} + async function killFileServer(): Promise { fileServer.close(); // Process.close() kills the file server process. However this termination @@ -166,3 +184,13 @@ test("contentType", async () => { assertEquals(contentType, "text/html"); (response.body as Deno.File).close(); }); + +test("file_server running as library", async function (): Promise { + await startFileServerAsLibrary(); + try { + const res = await fetch("http://localhost:8000"); + assertEquals(res.status, 200); + } finally { + await killFileServer(); + } +}); diff --git a/std/http/http_status.ts b/std/http/http_status.ts index ce4338705c1411..a0d0c278dc4bbb 100644 --- a/std/http/http_status.ts +++ b/std/http/http_status.ts @@ -8,7 +8,8 @@ export enum Status { SwitchingProtocols = 101, /** RFC 2518, 10.1 */ Processing = 102, - + /** RFC 8297 **/ + EarlyHints = 103, /** RFC 7231, 6.3.1 */ OK = 200, /** RFC 7231, 6.3.2 */ @@ -93,6 +94,8 @@ export enum Status { Locked = 423, /** RFC 4918, 11.4 */ FailedDependency = 424, + /** RFC 8470, 5.2 */ + TooEarly = 425, /** RFC 7231, 6.5.15 */ UpgradeRequired = 426, /** RFC 6585, 3 */ @@ -132,6 +135,7 @@ export const STATUS_TEXT = new Map([ [Status.Continue, "Continue"], [Status.SwitchingProtocols, "Switching Protocols"], [Status.Processing, "Processing"], + [Status.EarlyHints, "Early Hints"], [Status.OK, "OK"], [Status.Created, "Created"], [Status.Accepted, "Accepted"], @@ -173,6 +177,7 @@ export const STATUS_TEXT = new Map([ [Status.UnprocessableEntity, "Unprocessable Entity"], [Status.Locked, "Locked"], [Status.FailedDependency, "Failed Dependency"], + [Status.TooEarly, "Too Early"], [Status.UpgradeRequired, "Upgrade Required"], [Status.PreconditionRequired, "Precondition Required"], [Status.TooManyRequests, "Too Many Requests"], diff --git a/std/http/server.ts b/std/http/server.ts index 787820a563bd74..faf5da6af11b36 100644 --- a/std/http/server.ts +++ b/std/http/server.ts @@ -53,18 +53,9 @@ export class ServerRequest { private _body: Deno.Reader | null = null; /** - * Body of the request. + * Body of the request. The easiest way to consume the body is: * - * const buf = new Uint8Array(req.contentLength); - * let bufSlice = buf; - * let totRead = 0; - * while (true) { - * const nread = await req.body.read(bufSlice); - * if (nread === null) break; - * totRead += nread; - * if (totRead >= req.contentLength) break; - * bufSlice = bufSlice.subarray(nread); - * } + * const buf: Uint8Array = await Deno.readAll(req.body); */ get body(): Deno.Reader { if (!this._body) { diff --git a/std/http/testdata/file_server_as_library.ts b/std/http/testdata/file_server_as_library.ts new file mode 100644 index 00000000000000..ab5fdcaabe5ceb --- /dev/null +++ b/std/http/testdata/file_server_as_library.ts @@ -0,0 +1,12 @@ +import { serve } from "../server.ts"; +import { serveFile } from "../file_server.ts"; + +const server = serve({ port: 8000 }); + +console.log('Server running...'); + +for await (const req of server) { + serveFile(req, './http/testdata/hello.html').then(response => { + req.respond(response); + }); +} \ No newline at end of file diff --git a/std/io/readers.ts b/std/io/readers.ts index 10069986cef510..201b87cd8f621a 100644 --- a/std/io/readers.ts +++ b/std/io/readers.ts @@ -1,4 +1,10 @@ // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. + +// Based on https://github.com/golang/go/blob/0452f9460f50f0f0aba18df43dc2b31906fb66cc/src/io/io.go +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + type Reader = Deno.Reader; import { encode } from "../encoding/utf8.ts"; @@ -40,3 +46,30 @@ export class MultiReader implements Reader { return result; } } + +/** + * A `LimitedReader` reads from `reader` but limits the amount of data returned to just `limit` bytes. + * Each call to `read` updates `limit` to reflect the new amount remaining. + * `read` returns `null` when `limit` <= `0` or + * when the underlying `reader` returns `null`. + */ +export class LimitedReader implements Deno.Reader { + constructor(public reader: Deno.Reader, public limit: number) {} + + async read(p: Uint8Array): Promise { + if (this.limit <= 0) { + return null; + } + + if (p.length > this.limit) { + p = p.subarray(0, this.limit); + } + const n = await this.reader.read(p); + if (n == null) { + return null; + } + + this.limit -= n; + return n; + } +} diff --git a/std/io/readers_test.ts b/std/io/readers_test.ts index b0810f9e0639e0..04e9b7488d35af 100644 --- a/std/io/readers_test.ts +++ b/std/io/readers_test.ts @@ -1,6 +1,6 @@ const { copy, test } = Deno; import { assertEquals } from "../testing/asserts.ts"; -import { MultiReader, StringReader } from "./readers.ts"; +import { LimitedReader, MultiReader, StringReader } from "./readers.ts"; import { StringWriter } from "./writers.ts"; import { copyN } from "./ioutil.ts"; import { decode } from "../encoding/utf8.ts"; @@ -36,3 +36,28 @@ test("ioMultiReader", async function (): Promise { await copy(r, w); assertEquals(w.toString(), "abcdef"); }); + +test("ioLimitedReader", async function (): Promise { + let sr = new StringReader("abc"); + let r = new LimitedReader(sr, 2); + let buffer = await Deno.readAll(r); + assertEquals(decode(buffer), "ab"); + assertEquals(decode(await Deno.readAll(sr)), "c"); + sr = new StringReader("abc"); + r = new LimitedReader(sr, 3); + buffer = await Deno.readAll(r); + assertEquals(decode(buffer), "abc"); + assertEquals((await Deno.readAll(r)).length, 0); + sr = new StringReader("abc"); + r = new LimitedReader(sr, 4); + buffer = await Deno.readAll(r); + assertEquals(decode(buffer), "abc"); + assertEquals((await Deno.readAll(r)).length, 0); +}); + +test("ioLimitedReader", async function (): Promise { + const rb = new StringReader("abc"); + const wb = new StringWriter(); + await Deno.copy(new LimitedReader(rb, -1), wb); + assertEquals(wb.toString(), ""); +}); diff --git a/std/log/README.md b/std/log/README.md index 55b2b6cdb319bf..4c69784f999c4e 100644 --- a/std/log/README.md +++ b/std/log/README.md @@ -107,7 +107,10 @@ interface HandlerOptions { #### `FileHandler` This handler will output to a file using an optional mode (default is `a`, e.g. -append). The file will grow indefinitely. This logger takes `FileOptions`: +append). The file will grow indefinitely. It uses a buffer for writing to file +and will automatically flush every 30 seconds, though you can trigger this +yourself with `fileHandler.flush()`. Log messages with a log level greater than +error are immediately flushed. This logger takes `FileOptions`: ```typescript interface FileHandlerOptions { @@ -148,6 +151,11 @@ backups to keep), `log.txt.1` would be renamed to `log.txt.2`, `log.txt` would be renamed to `log.txt.1` and finally `log.txt` would be created from scratch where the new log message would be written. +This handler uses a buffer for writing to file and will automatically flush +every 30 seconds, though you can trigger this yourself with +`fileHandler.flush()`. Log messages with a log level greater than error are +immediately flushed. + Options for this handler are: ```typescript diff --git a/std/log/handlers.ts b/std/log/handlers.ts index ffcd846cc69164..b7a8c9cae222aa 100644 --- a/std/log/handlers.ts +++ b/std/log/handlers.ts @@ -1,5 +1,5 @@ // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. -const { open, openSync, close, renameSync, statSync } = Deno; +const { open, openSync, close, renameSync, stat } = Deno; type File = Deno.File; type Writer = Deno.Writer; type OpenOptions = Deno.OpenOptions; @@ -7,6 +7,7 @@ import { getLevelByName, LevelName, LogLevels } from "./levels.ts"; import { LogRecord } from "./logger.ts"; import { red, yellow, blue, bold } from "../fmt/colors.ts"; import { existsSync, exists } from "../fs/exists.ts"; +import { BufWriterSync } from "../io/bufio.ts"; const DEFAULT_FORMATTER = "{levelName} {msg}"; type FormatterFunction = (logRecord: LogRecord) => string; @@ -99,11 +100,14 @@ interface FileHandlerOptions extends HandlerOptions { } export class FileHandler extends WriterHandler { - protected _file!: File; + protected _file: File | undefined; + protected _buf!: BufWriterSync; protected _filename: string; protected _mode: LogMode; protected _openOptions: OpenOptions; - #encoder = new TextEncoder(); + protected _encoder = new TextEncoder(); + #intervalId = -1; + #unloadCallback = (): Promise => this.destroy(); constructor(levelName: LevelName, options: FileHandlerOptions) { super(levelName, options); @@ -122,14 +126,39 @@ export class FileHandler extends WriterHandler { async setup(): Promise { this._file = await open(this._filename, this._openOptions); this._writer = this._file; + this._buf = new BufWriterSync(this._file); + + addEventListener("unload", this.#unloadCallback); + + // flush the buffer every 30 seconds + this.#intervalId = setInterval(() => this.flush(), 30 * 1000); + } + + handle(logRecord: LogRecord): void { + super.handle(logRecord); + + // Immediately flush if log level is higher than ERROR + if (logRecord.level > LogLevels.ERROR) { + this.flush(); + } } log(msg: string): void { - Deno.writeAllSync(this._file, this.#encoder.encode(msg + "\n")); + this._buf.writeSync(this._encoder.encode(msg + "\n")); + } + + flush(): void { + if (this._buf?.buffered() > 0) { + this._buf.flush(); + } } destroy(): Promise { - this._file.close(); + this.flush(); + this._file?.close(); + this._file = undefined; + removeEventListener("unload", this.#unloadCallback); + clearInterval(this.#intervalId); return Promise.resolve(); } } @@ -142,6 +171,7 @@ interface RotatingFileHandlerOptions extends FileHandlerOptions { export class RotatingFileHandler extends FileHandler { #maxBytes: number; #maxBackupCount: number; + #currentFileSize = 0; constructor(levelName: LevelName, options: RotatingFileHandlerOptions) { super(levelName, options); @@ -151,9 +181,11 @@ export class RotatingFileHandler extends FileHandler { async setup(): Promise { if (this.#maxBytes < 1) { + this.destroy(); throw new Error("maxBytes cannot be less than 1"); } if (this.#maxBackupCount < 1) { + this.destroy(); throw new Error("maxBackupCount cannot be less than 1"); } await super.setup(); @@ -170,29 +202,32 @@ export class RotatingFileHandler extends FileHandler { // Throw if any backups also exist for (let i = 1; i <= this.#maxBackupCount; i++) { if (await exists(this._filename + "." + i)) { - Deno.close(this._file.rid); + this.destroy(); throw new Deno.errors.AlreadyExists( "Backup log file " + this._filename + "." + i + " already exists" ); } } + } else { + this.#currentFileSize = (await stat(this._filename)).size; } } - handle(logRecord: LogRecord): void { - if (this.level > logRecord.level) return; + log(msg: string): void { + const msgByteLength = this._encoder.encode(msg).byteLength + 1; - const msg = this.format(logRecord); - const currentFileSize = statSync(this._filename).size; - if (currentFileSize + msg.length > this.#maxBytes) { + if (this.#currentFileSize + msgByteLength > this.#maxBytes) { this.rotateLogFiles(); + this.#currentFileSize = 0; } - return this.log(msg); + this._buf.writeSync(this._encoder.encode(msg + "\n")); + this.#currentFileSize += msgByteLength; } rotateLogFiles(): void { - close(this._file.rid); + this._buf.flush(); + close(this._file!.rid); for (let i = this.#maxBackupCount - 1; i >= 0; i--) { const source = this._filename + (i === 0 ? "" : "." + i); @@ -205,5 +240,6 @@ export class RotatingFileHandler extends FileHandler { this._file = openSync(this._filename, this._openOptions); this._writer = this._file; + this._buf = new BufWriterSync(this._file); } } diff --git a/std/log/handlers_test.ts b/std/log/handlers_test.ts index b69ef6eabd0c94..cb73fa56fa8965 100644 --- a/std/log/handlers_test.ts +++ b/std/log/handlers_test.ts @@ -1,6 +1,11 @@ // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. const { test } = Deno; -import { assert, assertEquals, assertThrowsAsync } from "../testing/asserts.ts"; +import { + assert, + assertEquals, + assertThrowsAsync, + assertNotEquals, +} from "../testing/asserts.ts"; import { LogLevels, LogLevelNames, @@ -115,18 +120,18 @@ test({ test({ name: "FileHandler with mode 'x' will throw if log file already exists", async fn() { - await assertThrowsAsync( - async () => { - Deno.writeFileSync(LOG_FILE, new TextEncoder().encode("hello world")); - const fileHandler = new FileHandler("WARNING", { - filename: LOG_FILE, - mode: "x", - }); - await fileHandler.setup(); - }, - Deno.errors.AlreadyExists, - "ile exists" - ); + const fileHandler = new FileHandler("WARNING", { + filename: LOG_FILE, + mode: "x", + }); + Deno.writeFileSync(LOG_FILE, new TextEncoder().encode("hello world")); + + await assertThrowsAsync(async () => { + await fileHandler.setup(); + }, Deno.errors.AlreadyExists); + + await fileHandler.destroy(); + Deno.removeSync(LOG_FILE); }, }); @@ -171,30 +176,32 @@ test({ name: "RotatingFileHandler with mode 'x' will throw if any log file already exists", async fn() { + Deno.writeFileSync( + LOG_FILE + ".3", + new TextEncoder().encode("hello world") + ); + const fileHandler = new RotatingFileHandler("WARNING", { + filename: LOG_FILE, + maxBytes: 50, + maxBackupCount: 3, + mode: "x", + }); await assertThrowsAsync( async () => { - Deno.writeFileSync( - LOG_FILE + ".3", - new TextEncoder().encode("hello world") - ); - const fileHandler = new RotatingFileHandler("WARNING", { - filename: LOG_FILE, - maxBytes: 50, - maxBackupCount: 3, - mode: "x", - }); await fileHandler.setup(); }, Deno.errors.AlreadyExists, "Backup log file " + LOG_FILE + ".3 already exists" ); + + fileHandler.destroy(); Deno.removeSync(LOG_FILE + ".3"); Deno.removeSync(LOG_FILE); }, }); test({ - name: "RotatingFileHandler with first rollover", + name: "RotatingFileHandler with first rollover, monitor step by step", async fn() { const fileHandler = new RotatingFileHandler("WARNING", { filename: LOG_FILE, @@ -205,16 +212,43 @@ test({ await fileHandler.setup(); fileHandler.handle(new LogRecord("AAA", [], LogLevels.ERROR)); // 'ERROR AAA\n' = 10 bytes + fileHandler.flush(); assertEquals((await Deno.stat(LOG_FILE)).size, 10); fileHandler.handle(new LogRecord("AAA", [], LogLevels.ERROR)); + fileHandler.flush(); assertEquals((await Deno.stat(LOG_FILE)).size, 20); fileHandler.handle(new LogRecord("AAA", [], LogLevels.ERROR)); + fileHandler.flush(); // Rollover occurred. Log file now has 1 record, rollover file has the original 2 assertEquals((await Deno.stat(LOG_FILE)).size, 10); assertEquals((await Deno.stat(LOG_FILE + ".1")).size, 20); + await fileHandler.destroy(); + + Deno.removeSync(LOG_FILE); + Deno.removeSync(LOG_FILE + ".1"); + }, +}); + +test({ + name: "RotatingFileHandler with first rollover, check all at once", + async fn() { + const fileHandler = new RotatingFileHandler("WARNING", { + filename: LOG_FILE, + maxBytes: 25, + maxBackupCount: 3, + mode: "w", + }); + await fileHandler.setup(); + + fileHandler.handle(new LogRecord("AAA", [], LogLevels.ERROR)); // 'ERROR AAA\n' = 10 bytes + fileHandler.handle(new LogRecord("AAA", [], LogLevels.ERROR)); + fileHandler.handle(new LogRecord("AAA", [], LogLevels.ERROR)); await fileHandler.destroy(); + assertEquals((await Deno.stat(LOG_FILE)).size, 10); + assertEquals((await Deno.stat(LOG_FILE + ".1")).size, 20); + Deno.removeSync(LOG_FILE); Deno.removeSync(LOG_FILE + ".1"); }, @@ -307,3 +341,82 @@ test({ ); }, }); + +test({ + name: "Window unload flushes buffer", + async fn() { + const fileHandler = new FileHandler("WARNING", { + filename: LOG_FILE, + mode: "w", + }); + await fileHandler.setup(); + fileHandler.handle(new LogRecord("AAA", [], LogLevels.ERROR)); // 'ERROR AAA\n' = 10 bytes + + assertEquals((await Deno.stat(LOG_FILE)).size, 0); + dispatchEvent(new Event("unload")); + assertEquals((await Deno.stat(LOG_FILE)).size, 10); + + Deno.removeSync(LOG_FILE); + }, +}); + +test({ + name: "RotatingFileHandler: rotate on byte length, not msg length", + async fn() { + const fileHandler = new RotatingFileHandler("WARNING", { + filename: LOG_FILE, + maxBytes: 7, + maxBackupCount: 1, + mode: "w", + }); + await fileHandler.setup(); + + const msg = "。"; + const msgLength = msg.length; + const msgByteLength = new TextEncoder().encode(msg).byteLength; + assertNotEquals(msgLength, msgByteLength); + assertEquals(msgLength, 1); + assertEquals(msgByteLength, 3); + + fileHandler.log(msg); // logs 4 bytes (including '\n') + fileHandler.log(msg); // max bytes is 7, but this would be 8. Rollover. + + await fileHandler.destroy(); + + const fileSize1 = (await Deno.stat(LOG_FILE)).size; + const fileSize2 = (await Deno.stat(LOG_FILE + ".1")).size; + + assertEquals(fileSize1, msgByteLength + 1); + assertEquals(fileSize2, msgByteLength + 1); + + Deno.removeSync(LOG_FILE); + Deno.removeSync(LOG_FILE + ".1"); + }, +}); + +test({ + name: "FileHandler: Critical logs trigger immediate flush", + async fn() { + const fileHandler = new FileHandler("WARNING", { + filename: LOG_FILE, + mode: "w", + }); + await fileHandler.setup(); + + fileHandler.handle(new LogRecord("AAA", [], LogLevels.ERROR)); + + // ERROR won't trigger immediate flush + const fileSize = (await Deno.stat(LOG_FILE)).size; + assertEquals(fileSize, 0); + + fileHandler.handle(new LogRecord("AAA", [], LogLevels.CRITICAL)); + + // CRITICAL will trigger immediate flush + const fileSize2 = (await Deno.stat(LOG_FILE)).size; + // ERROR record is 10 bytes, CRITICAL is 13 bytes + assertEquals(fileSize2, 23); + + await fileHandler.destroy(); + Deno.removeSync(LOG_FILE); + }, +}); diff --git a/std/mime/multipart.ts b/std/mime/multipart.ts index 75d418c92e3295..7f7d0c8eef820d 100644 --- a/std/mime/multipart.ts +++ b/std/mime/multipart.ts @@ -252,11 +252,13 @@ function skipLWSPChar(u: Uint8Array): Uint8Array { } export interface MultipartFormData { - file(key: string): FormFile | undefined; + file(key: string): FormFile | FormFile[] | undefined; value(key: string): string | undefined; - entries(): IterableIterator<[string, string | FormFile | undefined]>; + entries(): IterableIterator< + [string, string | FormFile | FormFile[] | undefined] + >; [Symbol.iterator](): IterableIterator< - [string, string | FormFile | undefined] + [string, string | FormFile | FormFile[] | undefined] >; /** Remove all tempfiles */ removeAll(): Promise; @@ -282,7 +284,7 @@ export class MultipartReader { * @param maxMemory maximum memory size to store file in memory. bytes. @default 10485760 (10MB) * */ async readForm(maxMemory = 10 << 20): Promise { - const fileMap = new Map(); + const fileMap = new Map(); const valueMap = new Map(); let maxValueBytes = maxMemory + (10 << 20); const buf = new Buffer(new Uint8Array(maxValueBytes)); @@ -307,7 +309,7 @@ export class MultipartReader { continue; } // file - let formFile: FormFile | undefined; + let formFile: FormFile | FormFile[] | undefined; const n = await copyN(p, buf, maxValueBytes); const contentType = p.headers.get("content-type"); assert(contentType != null, "content-type must be set"); @@ -343,7 +345,16 @@ export class MultipartReader { maxValueBytes -= n; } if (formFile) { - fileMap.set(p.formName, formFile); + const mapVal = fileMap.get(p.formName); + if (mapVal !== undefined) { + if (Array.isArray(mapVal)) { + mapVal.push(formFile); + } else { + fileMap.set(p.formName, [mapVal, formFile]); + } + } else { + fileMap.set(p.formName, formFile); + } } } return multipatFormData(fileMap, valueMap); @@ -411,17 +422,17 @@ export class MultipartReader { } function multipatFormData( - fileMap: Map, + fileMap: Map, valueMap: Map ): MultipartFormData { - function file(key: string): FormFile | undefined { + function file(key: string): FormFile | FormFile[] | undefined { return fileMap.get(key); } function value(key: string): string | undefined { return valueMap.get(key); } function* entries(): IterableIterator< - [string, string | FormFile | undefined] + [string, string | FormFile | FormFile[] | undefined] > { yield* fileMap; yield* valueMap; @@ -429,8 +440,15 @@ function multipatFormData( async function removeAll(): Promise { const promises: Array> = []; for (const val of fileMap.values()) { - if (!val.tempfile) continue; - promises.push(Deno.remove(val.tempfile)); + if (Array.isArray(val)) { + for (const subVal of val) { + if (!subVal.tempfile) continue; + promises.push(Deno.remove(subVal.tempfile)); + } + } else { + if (!val.tempfile) continue; + promises.push(Deno.remove(val.tempfile)); + } } await Promise.all(promises); } @@ -440,7 +458,7 @@ function multipatFormData( entries, removeAll, [Symbol.iterator](): IterableIterator< - [string, string | FormFile | undefined] + [string, string | FormFile | FormFile[] | undefined] > { return entries(); }, diff --git a/std/mime/multipart_test.ts b/std/mime/multipart_test.ts index b7c0cb969c5dcc..858dc3919a2e7a 100644 --- a/std/mime/multipart_test.ts +++ b/std/mime/multipart_test.ts @@ -145,8 +145,8 @@ test("multipartMultipartWriter3", async function (): Promise { ); await assertThrowsAsync( async (): Promise => { - // @ts-expect-error - await mw.writeFile("bar", "file", null); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + await mw.writeFile("bar", "file", null as any); }, Error, "closed" @@ -225,7 +225,10 @@ test({ try { assertEquals(form.value("deno"), "land"); assertEquals(form.value("bar"), "bar"); - const file = form.file("file"); + let file = form.file("file"); + if (Array.isArray(file)) { + file = file[0]; + } assert(file != null); assert(file.tempfile != null); assertEquals(file.size, size); @@ -249,7 +252,10 @@ test({ "--------------------------434049563556637648550474" ); const form = await mr.readForm(20); - const file = form.file("file"); + let file = form.file("file"); + if (Array.isArray(file)) { + file = file[0]; + } assert(file != null); const { tempfile, content } = file; assert(tempfile != null); diff --git a/std/node/_fs/_fs_link.ts b/std/node/_fs/_fs_link.ts new file mode 100644 index 00000000000000..50916a7ba0d7ef --- /dev/null +++ b/std/node/_fs/_fs_link.ts @@ -0,0 +1,37 @@ +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. + +import { CallbackWithError } from "./_fs_common.ts"; +import { fromFileUrl } from "../path.ts"; + +/** + * TODO: Also accept 'path' parameter as a Node polyfill Buffer type once these + * are implemented. See https://github.com/denoland/deno/issues/3403 + */ +export function link( + existingPath: string | URL, + newPath: string | URL, + callback: CallbackWithError +): void { + existingPath = + existingPath instanceof URL ? fromFileUrl(existingPath) : existingPath; + newPath = newPath instanceof URL ? fromFileUrl(newPath) : newPath; + + Deno.link(existingPath, newPath) + .then(() => callback()) + .catch(callback); +} + +/** + * TODO: Also accept 'path' parameter as a Node polyfill Buffer type once these + * are implemented. See https://github.com/denoland/deno/issues/3403 + */ +export function linkSync( + existingPath: string | URL, + newPath: string | URL +): void { + existingPath = + existingPath instanceof URL ? fromFileUrl(existingPath) : existingPath; + newPath = newPath instanceof URL ? fromFileUrl(newPath) : newPath; + + Deno.linkSync(existingPath, newPath); +} diff --git a/std/node/_fs/_fs_link_test.ts b/std/node/_fs/_fs_link_test.ts new file mode 100644 index 00000000000000..e59984c8cbcfb0 --- /dev/null +++ b/std/node/_fs/_fs_link_test.ts @@ -0,0 +1,67 @@ +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. +const { test } = Deno; +import { fail, assertEquals } from "../../testing/asserts.ts"; +import { link, linkSync } from "./_fs_link.ts"; +import { assert } from "https://deno.land/std@v0.50.0/testing/asserts.ts"; +const isWindows = Deno.build.os === "windows"; + +test({ + ignore: isWindows, + name: "ASYNC: hard linking files works as expected", + async fn() { + const tempFile: string = await Deno.makeTempFile(); + const linkedFile: string = tempFile + ".link"; + await new Promise((res, rej) => { + link(tempFile, linkedFile, (err) => { + if (err) rej(err); + else res(); + }); + }) + .then(() => { + assertEquals(Deno.statSync(tempFile), Deno.statSync(linkedFile)); + }) + .catch(() => { + fail("Expected to succeed"); + }) + .finally(() => { + Deno.removeSync(tempFile); + Deno.removeSync(linkedFile); + }); + }, +}); + +test({ + ignore: isWindows, + name: "ASYNC: hard linking files passes error to callback", + async fn() { + let failed = false; + await new Promise((res, rej) => { + link("no-such-file", "no-such-file", (err) => { + if (err) rej(err); + else res(); + }); + }) + .then(() => { + fail("Expected to succeed"); + }) + .catch((err) => { + assert(err); + failed = true; + }); + assert(failed); + }, +}); + +test({ + ignore: isWindows, + name: "SYNC: hard linking files works as expected", + fn() { + const tempFile: string = Deno.makeTempFileSync(); + const linkedFile: string = tempFile + ".link"; + linkSync(tempFile, linkedFile); + + assertEquals(Deno.statSync(tempFile), Deno.statSync(linkedFile)); + Deno.removeSync(tempFile); + Deno.removeSync(linkedFile); + }, +}); diff --git a/std/node/_util/_util_types.ts b/std/node/_util/_util_types.ts new file mode 100644 index 00000000000000..387f22f97c8b5e --- /dev/null +++ b/std/node/_util/_util_types.ts @@ -0,0 +1,31 @@ +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. +// +// Adapted from Node.js. Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +const _toString = Object.prototype.toString; + +const _isObjectLike = (value: unknown): boolean => + value !== null && typeof value === "object"; + +export function isDate(value: unknown): boolean { + return _isObjectLike(value) && _toString.call(value) === "[object Date]"; +} diff --git a/std/node/_util/_util_types_test.ts b/std/node/_util/_util_types_test.ts new file mode 100644 index 00000000000000..39be8200dbf850 --- /dev/null +++ b/std/node/_util/_util_types_test.ts @@ -0,0 +1,60 @@ +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. +// +// Adapted from Node.js. Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +import { assertStrictEq } from "../../testing/asserts.ts"; + +import { isDate } from "./_util_types.ts"; + +const { test } = Deno; + +test("New date instance with no arguments", () => { + assertStrictEq(isDate(new Date()), true); +}); + +test("New date instance with value 0", () => { + assertStrictEq(isDate(new Date(0)), true); +}); + +test("New date instance in new context", () => { + assertStrictEq(isDate(new (eval("Date"))()), true); +}); + +test("Date function is not of type Date", () => { + assertStrictEq(isDate(Date()), false); +}); + +test("Object is not of type Date", () => { + assertStrictEq(isDate({}), false); +}); + +test("Array is not of type Date", () => { + assertStrictEq(isDate([]), false); +}); + +test("Error is not of type Date", () => { + assertStrictEq(isDate(new Error()), false); +}); + +test("New object from Date prototype is not of type Date", () => { + assertStrictEq(isDate(Object.create(Date.prototype)), false); +}); diff --git a/std/node/global.ts b/std/node/global.ts index 7d147cc4b1de8d..0cb6b8b06bfa27 100644 --- a/std/node/global.ts +++ b/std/node/global.ts @@ -5,5 +5,5 @@ Object.defineProperty(globalThis, Symbol.toStringTag, { configurable: true, }); -// @ts-expect-error -globalThis["global"] = globalThis; +// eslint-disable-next-line @typescript-eslint/no-explicit-any +(globalThis as any)["global"] = globalThis; diff --git a/std/node/module.ts b/std/node/module.ts index 71c25beacaf8de..13b2ac325ee73e 100644 --- a/std/node/module.ts +++ b/std/node/module.ts @@ -262,10 +262,11 @@ class Module { if (requireStack.length > 0) { message = message + "\nRequire stack:\n- " + requireStack.join("\n- "); } - const err = new Error(message); - // @ts-expect-error + const err = new Error(message) as Error & { + code: string; + requireStack: string[]; + }; err.code = "MODULE_NOT_FOUND"; - // @ts-expect-error err.requireStack = requireStack; throw err; } @@ -732,12 +733,10 @@ function tryPackage( if (actual === false) { actual = tryExtensions(path.resolve(requestPath, "index"), exts, isMain); if (!actual) { - // eslint-disable-next-line no-restricted-syntax const err = new Error( `Cannot find module '${filename}'. ` + 'Please verify that the package.json has a valid "main" entry' - ); - // @ts-expect-error + ) as Error & { code: string }; err.code = "MODULE_NOT_FOUND"; throw err; } @@ -881,8 +880,7 @@ function applyExports(basePath: string, expansion: string): string { const e = new Error( `Package exports for '${basePath}' do not define ` + `a '${mappingKey}' subpath` - ); - // @ts-expect-error + ) as Error & { code?: string }; e.code = "MODULE_NOT_FOUND"; throw e; } @@ -973,7 +971,7 @@ function resolveExportsTarget( } } } - let e: Error; + let e: Error & { code?: string }; if (mappingKey !== ".") { e = new Error( `Package exports for '${basePath}' do not define a ` + @@ -982,7 +980,6 @@ function resolveExportsTarget( } else { e = new Error(`No valid exports main found for '${basePath}'`); } - // @ts-expect-error e.code = "MODULE_NOT_FOUND"; throw e; } @@ -1006,8 +1003,7 @@ const CircularRequirePrototypeWarningProxy = new Proxy( {}, { // eslint-disable-next-line @typescript-eslint/no-explicit-any - get(target, prop): any { - // @ts-expect-error + get(target: Record, prop: string): any { if (prop in target) return target[prop]; emitCircularRequireWarning(prop); return undefined; @@ -1058,8 +1054,8 @@ type RequireWrapper = ( function wrapSafe(filename: string, content: string): RequireWrapper { // TODO: fix this const wrapper = Module.wrap(content); - // @ts-expect-error - const [f, err] = Deno.core.evalContext(wrapper, filename); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const [f, err] = (Deno as any).core.evalContext(wrapper, filename); if (err) { throw err; } diff --git a/std/node/util.ts b/std/node/util.ts index 8c3fdabe877491..b115b061bc2b9a 100644 --- a/std/node/util.ts +++ b/std/node/util.ts @@ -1,4 +1,7 @@ export { callbackify } from "./_util/_util_callbackify.ts"; +import * as types from "./_util/_util_types.ts"; + +export { types }; export function isArray(value: unknown): boolean { return Array.isArray(value); diff --git a/std/node/util_test.ts b/std/node/util_test.ts index 7036368b622a1d..b6439644101c43 100644 --- a/std/node/util_test.ts +++ b/std/node/util_test.ts @@ -166,3 +166,11 @@ test({ assert(te instanceof TextEncoder); }, }); + +test({ + name: "[util] isDate", + fn() { + // Test verifies the method is exposed. See _util/_util_types_test for details + assert(util.types.isDate(new Date())); + }, +}); diff --git a/std/path/_constants.ts b/std/path/_constants.ts index ae0aac18465c0d..186c32ab532635 100644 --- a/std/path/_constants.ts +++ b/std/path/_constants.ts @@ -1,7 +1,6 @@ // Copyright the Browserify authors. MIT License. // Ported from https://github.com/browserify/path-browserify/ - -const { build } = Deno; +/** This module is browser compatible. */ // Alphabet chars. export const CHAR_UPPERCASE_A = 65; /* A */ @@ -48,7 +47,14 @@ export const CHAR_EQUAL = 61; /* = */ export const CHAR_0 = 48; /* 0 */ export const CHAR_9 = 57; /* 9 */ -const isWindows = build.os == "windows"; +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const navigator = (globalThis as any).navigator; + +let isWindows = false; +if (globalThis.Deno != null) { + isWindows = Deno.build.os == "windows"; +} else if (navigator?.appVersion != null) { + isWindows = navigator.appVersion.includes("Win"); +} -export const SEP = isWindows ? "\\" : "/"; -export const SEP_PATTERN = isWindows ? /[\\/]+/ : /\/+/; +export { isWindows }; diff --git a/std/path/_globrex.ts b/std/path/_globrex.ts index 2b3af14ad0b333..6ad297d8643df5 100644 --- a/std/path/_globrex.ts +++ b/std/path/_globrex.ts @@ -1,8 +1,10 @@ // This file is ported from globrex@0.1.2 // MIT License // Copyright (c) 2018 Terkel Gjervig Nielsen +/** This module is browser compatible. */ + +import { isWindows as isWin } from "./_constants.ts"; -const isWin = Deno.build.os === "windows"; const SEP = isWin ? `(?:\\\\|\\/)` : `\\/`; const SEP_ESC = isWin ? `\\\\` : `/`; const SEP_RAW = isWin ? `\\` : `/`; diff --git a/std/path/interface.ts b/std/path/_interface.ts similarity index 93% rename from std/path/interface.ts rename to std/path/_interface.ts index b31c89ea732062..6c82c9c35cf452 100644 --- a/std/path/interface.ts +++ b/std/path/_interface.ts @@ -1,3 +1,5 @@ +/** This module is browser compatible. */ + /** * A parsed path object generated by path.parse() or consumed by path.format(). */ diff --git a/std/path/_util.ts b/std/path/_util.ts index 2776303cb6ed06..8ae40373b21971 100644 --- a/std/path/_util.ts +++ b/std/path/_util.ts @@ -1,7 +1,8 @@ // Copyright the Browserify authors. MIT License. // Ported from https://github.com/browserify/path-browserify/ +/** This module is browser compatible. */ -import { FormatInputPathObject } from "./interface.ts"; +import { FormatInputPathObject } from "./_interface.ts"; import { CHAR_UPPERCASE_A, CHAR_LOWERCASE_A, diff --git a/std/path/common.ts b/std/path/common.ts index e0e51ef239108f..01470105ca91a2 100644 --- a/std/path/common.ts +++ b/std/path/common.ts @@ -1,4 +1,5 @@ // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. +/** This module is browser compatible. */ import { SEP } from "./separator.ts"; diff --git a/std/path/glob.ts b/std/path/glob.ts index 80672579d2c684..34fc213f629deb 100644 --- a/std/path/glob.ts +++ b/std/path/glob.ts @@ -1,3 +1,6 @@ +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. +/** This module is browser compatible. */ + import { SEP, SEP_PATTERN } from "./separator.ts"; import { globrex } from "./_globrex.ts"; import { join, normalize } from "./mod.ts"; diff --git a/std/path/mod.ts b/std/path/mod.ts index 9cb7f1edb2fa2e..0b4156e69981eb 100644 --- a/std/path/mod.ts +++ b/std/path/mod.ts @@ -1,11 +1,11 @@ // Copyright the Browserify authors. MIT License. // Ported mostly from https://github.com/browserify/path-browserify/ +/** This module is browser compatible. */ +import { isWindows } from "./_constants.ts"; import * as _win32 from "./win32.ts"; import * as _posix from "./posix.ts"; -const isWindows = Deno.build.os == "windows"; - const path = isWindows ? _win32 : _posix; export const win32 = _win32; @@ -29,5 +29,5 @@ export const { export * from "./common.ts"; export { SEP, SEP_PATTERN } from "./separator.ts"; -export * from "./interface.ts"; +export * from "./_interface.ts"; export * from "./glob.ts"; diff --git a/std/path/posix.ts b/std/path/posix.ts index e88eb3f973ea17..365232e336f684 100644 --- a/std/path/posix.ts +++ b/std/path/posix.ts @@ -1,8 +1,8 @@ // Copyright the Browserify authors. MIT License. // Ported from https://github.com/browserify/path-browserify/ +/** This module is browser compatible. */ -const { cwd } = Deno; -import { FormatInputPathObject, ParsedPath } from "./interface.ts"; +import { FormatInputPathObject, ParsedPath } from "./_interface.ts"; import { CHAR_DOT, CHAR_FORWARD_SLASH } from "./_constants.ts"; import { @@ -24,7 +24,12 @@ export function resolve(...pathSegments: string[]): string { let path: string; if (i >= 0) path = pathSegments[i]; - else path = cwd(); + else { + if (globalThis.Deno == null) { + throw new TypeError("Resolved a relative path without a CWD."); + } + path = Deno.cwd(); + } assertPath(path); diff --git a/std/path/separator.ts b/std/path/separator.ts index fb990b8086d002..4b54ad4384b4a8 100644 --- a/std/path/separator.ts +++ b/std/path/separator.ts @@ -1,4 +1,7 @@ // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. -const isWindows = Deno.build.os == "windows"; +/** This module is browser compatible. */ + +import { isWindows } from "./_constants.ts"; + export const SEP = isWindows ? "\\" : "/"; export const SEP_PATTERN = isWindows ? /[\\/]+/ : /\/+/; diff --git a/std/path/win32.ts b/std/path/win32.ts index 0557b3768ee079..f556c5b736adf2 100644 --- a/std/path/win32.ts +++ b/std/path/win32.ts @@ -1,8 +1,8 @@ // Copyright the Browserify authors. MIT License. // Ported from https://github.com/browserify/path-browserify/ +/** This module is browser compatible. */ -const { cwd, env } = Deno; -import { FormatInputPathObject, ParsedPath } from "./interface.ts"; +import { FormatInputPathObject, ParsedPath } from "./_interface.ts"; import { CHAR_DOT, CHAR_BACKWARD_SLASH, @@ -32,14 +32,20 @@ export function resolve(...pathSegments: string[]): string { if (i >= 0) { path = pathSegments[i]; } else if (!resolvedDevice) { - path = cwd(); + if (globalThis.Deno == null) { + throw new TypeError("Resolved a drive-letter-less path without a CWD."); + } + path = Deno.cwd(); } else { + if (globalThis.Deno == null) { + throw new TypeError("Resolved a relative path without a CWD."); + } // Windows has the concept of drive-specific current working // directories. If we've resolved a drive letter but not yet an // absolute path, get cwd for that drive, or the process cwd if // the drive cwd is not available. We're sure the device is not // a UNC path at this points, because UNC paths are always absolute. - path = env.get(`=${resolvedDevice}`) || cwd(); + path = Deno.env.get(`=${resolvedDevice}`) || Deno.cwd(); // Verify that a cwd was found and that it actually points // to our drive. If not, default to the drive's root. diff --git a/std/signal/test.ts b/std/signal/test.ts index 13c2998db1f8b8..ef79a303bcf80f 100644 --- a/std/signal/test.ts +++ b/std/signal/test.ts @@ -9,8 +9,8 @@ test({ fn() { assertThrows( () => { - // @ts-expect-error - signal(); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (signal as any)(); }, Error, "No signals are given. You need to specify at least one signal to create a signal stream." diff --git a/std/testing/README.md b/std/testing/README.md index 66d80d4bc0991c..42ce49d5c8dbc4 100644 --- a/std/testing/README.md +++ b/std/testing/README.md @@ -137,9 +137,20 @@ Deno.test("fails", async function (): Promise { }); ``` -### Benching Usage +## Benching -Basic usage: +With this module you can benchmark your code and get information on how is it +performing. + +### Basic usage: + +Benchmarks can be registered using the `bench` function, where you can define a +code, that should be benchmarked. `b.start()` has to be called at the start of +the part you want to benchmark and `b.stop()` at the end of it, otherwise an +error will be thrown. + +After that simply calling `runBenchmarks()` will benchmark all registered +benchmarks and log the results in the commandline. ```ts import { runBenchmarks, bench } from "https://deno.land/std/testing/bench.ts"; @@ -167,47 +178,63 @@ bench({ }); ``` +Running specific benchmarks using regular expressions: + +```ts +runBenchmarks({ only: /desired/, skip: /exceptions/ }); +``` + +### Processing benchmark results + +`runBenchmarks()` returns a `Promise`, so you can process +the benchmarking results yourself. It contains detailed results of each +benchmark's run as `BenchmarkResult` s. + +```ts +runBenchmarks() + .then((results: BenchmarkRunResult) => { + console.log(results); + }) + .catch((error: Error) => { + // ... errors if benchmark was badly constructed + }); +``` + +### Processing benchmarking progress + +`runBenchmarks()` accepts an optional progress handler callback function, so you +can get information on the progress of the running benchmarking. + +Using `{ silent: true }` means you wont see the default progression logs in the +commandline. + +```ts +runBenchmarks({ silent: true }, (p: BenchmarkRunProgress) => { + // initial progress data + if (p.state === ProgressState.BenchmarkingStart) { + console.log( + `Starting benchmarking. Queued: ${p.queued.length}, filtered: ${p.filtered}` + ); + } + // ... +}); +``` + #### Benching API ##### `bench(benchmark: BenchmarkDefinition | BenchmarkFunction): void` Registers a benchmark that will be run once `runBenchmarks` is called. -##### `runBenchmarks(opts?: BenchmarkRunOptions): Promise` +##### `runBenchmarks(opts?: BenchmarkRunOptions, progressCb?: (p: BenchmarkRunProgress) => void): Promise` Runs all registered benchmarks serially. Filtering can be applied by setting `BenchmarkRunOptions.only` and/or `BenchmarkRunOptions.skip` to regular -expressions matching benchmark names. - -##### `runIfMain(meta: ImportMeta, opts?: BenchmarkRunOptions): Promise` +expressions matching benchmark names. Default progression logs can be turned off +with the `BenchmarkRunOptions.silent` flag. -Runs specified benchmarks if the enclosing script is main. +##### `clearBenchmarks(opts?: BenchmarkClearOptions): void` -##### Other exports - -```ts -/** Provides methods for starting and stopping a benchmark clock. */ -export interface BenchmarkTimer { - start: () => void; - stop: () => void; -} - -/** Defines a benchmark through a named function. */ -export interface BenchmarkFunction { - (b: BenchmarkTimer): void | Promise; - name: string; -} - -/** Defines a benchmark definition with configurable runs. */ -export interface BenchmarkDefinition { - func: BenchmarkFunction; - name: string; - runs?: number; -} - -/** Defines runBenchmark's run constraints by matching benchmark names. */ -export interface BenchmarkRunOptions { - only?: RegExp; - skip?: RegExp; -} -``` +Clears all registered benchmarks, so calling `runBenchmarks()` after it wont run +them. Filtering can be applied by setting `BenchmarkRunOptions.only` and/or +`BenchmarkRunOptions.skip` to regular expressions matching benchmark names. diff --git a/std/testing/asserts.ts b/std/testing/asserts.ts index ce721499847771..5f5c3a7c5eebb0 100644 --- a/std/testing/asserts.ts +++ b/std/testing/asserts.ts @@ -2,7 +2,7 @@ /** This module is browser compatible. Do not rely on good formatting of values * for AssertionError messages in browsers. */ -import { red, green, white, gray, bold } from "../fmt/colors.ts"; +import { red, green, white, gray, bold, stripColor } from "../fmt/colors.ts"; import diff, { DiffType, DiffResult } from "./diff.ts"; const CAN_NOT_DISPLAY = "[Cannot display]"; @@ -289,9 +289,9 @@ export function assertArrayContains( return; } if (!msg) { - msg = `actual: "${actual}" expected to contain: "${expected}"`; - msg += "\n"; - msg += `missing: ${missing}`; + msg = `actual: "${format(actual)}" expected to contain: "${format( + expected + )}"\nmissing: ${format(missing)}`; } throw new AssertionError(msg); } @@ -325,8 +325,8 @@ export function fail(msg?: string): void { * throws. An error class and a string that should be included in the * error message can also be asserted. */ -export function assertThrows( - fn: () => void, +export function assertThrows( + fn: () => T, ErrorClass?: Constructor, msgIncludes = "", msg?: string @@ -342,7 +342,10 @@ export function assertThrows( }"${msg ? `: ${msg}` : "."}`; throw new AssertionError(msg); } - if (msgIncludes && !e.message.includes(msgIncludes)) { + if ( + msgIncludes && + !stripColor(e.message).includes(stripColor(msgIncludes)) + ) { msg = `Expected error message to include "${msgIncludes}", but got "${ e.message }"${msg ? `: ${msg}` : "."}`; @@ -358,8 +361,8 @@ export function assertThrows( return error; } -export async function assertThrowsAsync( - fn: () => Promise, +export async function assertThrowsAsync( + fn: () => Promise, ErrorClass?: Constructor, msgIncludes = "", msg?: string @@ -375,7 +378,10 @@ export async function assertThrowsAsync( }"${msg ? `: ${msg}` : "."}`; throw new AssertionError(msg); } - if (msgIncludes && !e.message.includes(msgIncludes)) { + if ( + msgIncludes && + !stripColor(e.message).includes(stripColor(msgIncludes)) + ) { msg = `Expected error message to include "${msgIncludes}", but got "${ e.message }"${msg ? `: ${msg}` : "."}`; diff --git a/std/testing/asserts_test.ts b/std/testing/asserts_test.ts index fb25d46cf74b9a..3969cd661b804e 100644 --- a/std/testing/asserts_test.ts +++ b/std/testing/asserts_test.ts @@ -9,6 +9,7 @@ import { assertEquals, assertStrictEq, assertThrows, + assertThrowsAsync, AssertionError, equal, fail, @@ -151,15 +152,11 @@ test("testingArrayContains", function (): void { const fixtureObject = [{ deno: "luv" }, { deno: "Js" }]; assertArrayContains(fixture, ["deno"]); assertArrayContains(fixtureObject, [{ deno: "luv" }]); - let didThrow; - try { - assertArrayContains(fixtureObject, [{ deno: "node" }]); - didThrow = false; - } catch (e) { - assert(e instanceof AssertionError); - didThrow = true; - } - assertEquals(didThrow, true); + assertThrows( + (): void => assertArrayContains(fixtureObject, [{ deno: "node" }]), + AssertionError, + `actual: "[ { deno: "luv" }, { deno: "Js" } ]" expected to contain: "[ { deno: "node" } ]"\nmissing: [ { deno: "node" } ]` + ); }); test("testingAssertStringContainsThrow", function (): void { @@ -249,6 +246,20 @@ test("testingAssertFailWithWrongErrorClass", function (): void { ); }); +test("testingAssertThrowsWithReturnType", () => { + assertThrows(() => { + throw new Error(); + return "a string"; + }); +}); + +test("testingAssertThrowsAsyncWithReturnType", () => { + assertThrowsAsync(() => { + throw new Error(); + return Promise.resolve("a Promise"); + }); +}); + const createHeader = (): string[] => [ "", "", diff --git a/std/testing/bench.ts b/std/testing/bench.ts index cd7b89e8c409a8..87be641abeaed9 100644 --- a/std/testing/bench.ts +++ b/std/testing/bench.ts @@ -1,10 +1,12 @@ // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. +import { deepAssign } from "../_util/deep_assign.ts"; -const { exit, noColor } = Deno; +const { noColor } = Deno; interface BenchmarkClock { start: number; stop: number; + for?: string; } /** Provides methods for starting and stopping a benchmark clock. */ @@ -23,13 +25,76 @@ export interface BenchmarkFunction { export interface BenchmarkDefinition { func: BenchmarkFunction; name: string; + /** Defines how many times the provided `func` should be benchmarked in succession */ runs?: number; } /** Defines runBenchmark's run constraints by matching benchmark names. */ export interface BenchmarkRunOptions { + /** Only benchmarks which name match this regexp will be run*/ only?: RegExp; + /** Benchmarks which name match this regexp will be skipped */ skip?: RegExp; + /** Setting it to true prevents default benchmarking progress logs to the commandline*/ + silent?: boolean; +} + +/** Defines clearBenchmark's constraints by matching benchmark names. */ +export interface BenchmarkClearOptions { + /** Only benchmarks which name match this regexp will be removed */ + only?: RegExp; + /** Benchmarks which name match this regexp will be kept */ + skip?: RegExp; +} + +/** Defines the result of a single benchmark */ +export interface BenchmarkResult { + /** The name of the benchmark */ + name: string; + /** The total time it took to run a given bechmark */ + totalMs: number; + /** Times the benchmark was run in succession. */ + runsCount: number; + /** The average time of running the benchmark in milliseconds. */ + measuredRunsAvgMs: number; + /** The individual measurements in milliseconds it took to run the benchmark.*/ + measuredRunsMs: number[]; +} + +/** Defines the result of a `runBenchmarks` call */ +export interface BenchmarkRunResult { + /** How many benchmark were ignored by the provided `only` and `skip` */ + filtered: number; + /** The individual results for each benchmark that was run */ + results: BenchmarkResult[]; +} + +/** Defines the current progress during the run of `runBenchmarks` */ +export interface BenchmarkRunProgress extends BenchmarkRunResult { + /** List of the queued benchmarks to run with their name and their run count */ + queued: Array<{ name: string; runsCount: number }>; + /** The currently running benchmark with its name, run count and the already finished measurements in milliseconds */ + running?: { name: string; runsCount: number; measuredRunsMs: number[] }; + /** Indicates in which state benchmarking currently is */ + state: ProgressState; +} + +/** Defines the states `BenchmarkRunProgress` can be in */ +export enum ProgressState { + BenchmarkingStart = "benchmarking_start", + BenchStart = "bench_start", + BenchPartialResult = "bench_partial_result", + BenchResult = "bench_result", + BenchmarkingEnd = "benchmarking_end", +} + +export class BenchmarkRunError extends Error { + benchmarkName?: string; + constructor(msg: string, benchmarkName?: string) { + super(msg); + this.name = "BenchmarkRunError"; + this.benchmarkName = benchmarkName; + } } function red(text: string): string { @@ -47,13 +112,19 @@ function verifyOr1Run(runs?: number): number { function assertTiming(clock: BenchmarkClock): void { // NaN indicates that a benchmark has not been timed properly if (!clock.stop) { - throw new Error("The benchmark timer's stop method must be called"); + throw new BenchmarkRunError( + `Running benchmarks FAILED during benchmark named [${clock.for}]. The benchmark timer's stop method must be called`, + clock.for + ); } else if (!clock.start) { - throw new Error("The benchmark timer's start method must be called"); + throw new BenchmarkRunError( + `Running benchmarks FAILED during benchmark named [${clock.for}]. The benchmark timer's start method must be called`, + clock.for + ); } else if (clock.start > clock.stop) { - throw new Error( - "The benchmark timer's start method must be called before its " + - "stop method" + throw new BenchmarkRunError( + `Running benchmarks FAILED during benchmark named [${clock.for}]. The benchmark timer's start method must be called before its stop method`, + clock.for ); } } @@ -64,6 +135,12 @@ function createBenchmarkTimer(clock: BenchmarkClock): BenchmarkTimer { clock.start = performance.now(); }, stop(): void { + if (isNaN(clock.start)) { + throw new BenchmarkRunError( + `Running benchmarks FAILED during benchmark named [${clock.for}]. The benchmark timer's start method must be called before its stop method`, + clock.for + ); + } clock.stop = performance.now(); }, }; @@ -89,92 +166,191 @@ export function bench( } } -/** Runs all registered and non-skipped benchmarks serially. */ -export async function runBenchmarks({ +/** Clears benchmark candidates which name matches `only` and doesn't match `skip`. + * Removes all candidates if options were not provided */ +export function clearBenchmarks({ only = /[^\s]/, - skip = /^\s*$/, -}: BenchmarkRunOptions = {}): Promise { + skip = /$^/, +}: BenchmarkClearOptions = {}): void { + const keep = candidates.filter( + ({ name }): boolean => !only.test(name) || skip.test(name) + ); + candidates.splice(0, candidates.length); + candidates.push(...keep); +} + +/** + * Runs all registered and non-skipped benchmarks serially. + * + * @param [progressCb] provides the possibility to get updates of the current progress during the run of the benchmarking + * @returns results of the benchmarking + */ +export async function runBenchmarks( + { only = /[^\s]/, skip = /^\s*$/, silent }: BenchmarkRunOptions = {}, + progressCb?: (progress: BenchmarkRunProgress) => void +): Promise { // Filtering candidates by the "only" and "skip" constraint const benchmarks: BenchmarkDefinition[] = candidates.filter( ({ name }): boolean => only.test(name) && !skip.test(name) ); // Init main counters and error flag const filtered = candidates.length - benchmarks.length; - let measured = 0; - let failed = false; + let failError: Error | undefined = undefined; // Setting up a shared benchmark clock and timer const clock: BenchmarkClock = { start: NaN, stop: NaN }; const b = createBenchmarkTimer(clock); + + // Init progress data + const progress: BenchmarkRunProgress = { + // bench.run is already ensured with verifyOr1Run on register + queued: benchmarks.map((bench) => ({ + name: bench.name, + runsCount: bench.runs!, + })), + results: [], + filtered, + state: ProgressState.BenchmarkingStart, + }; + + // Publish initial progress data + publishProgress(progress, ProgressState.BenchmarkingStart, progressCb); + + if (!silent) { + console.log( + "running", + benchmarks.length, + `benchmark${benchmarks.length === 1 ? " ..." : "s ..."}` + ); + } + // Iterating given benchmark definitions (await-in-loop) - console.log( - "running", - benchmarks.length, - `benchmark${benchmarks.length === 1 ? " ..." : "s ..."}` - ); for (const { name, runs = 0, func } of benchmarks) { - // See https://github.com/denoland/deno/pull/1452 about groupCollapsed - console.groupCollapsed(`benchmark ${name} ... `); + if (!silent) { + // See https://github.com/denoland/deno/pull/1452 about groupCollapsed + console.groupCollapsed(`benchmark ${name} ... `); + } + + // Provide the benchmark name for clock assertions + clock.for = name; + + // Remove benchmark from queued + const queueIndex = progress.queued.findIndex( + (queued) => queued.name === name && queued.runsCount === runs + ); + if (queueIndex != -1) { + progress.queued.splice(queueIndex, 1); + } + // Init the progress of the running benchmark + progress.running = { name, runsCount: runs, measuredRunsMs: [] }; + // Publish starting of a benchmark + publishProgress(progress, ProgressState.BenchStart, progressCb); + // Trying benchmark.func let result = ""; try { - if (runs === 1) { + // Averaging runs + let pendingRuns = runs; + let totalMs = 0; + + // Would be better 2 not run these serially + while (true) { // b is a benchmark timer interfacing an unset (NaN) benchmark clock await func(b); // Making sure the benchmark was started/stopped properly assertTiming(clock); - result = `${clock.stop - clock.start}ms`; - } else if (runs > 1) { - // Averaging runs - let pendingRuns = runs; - let totalMs = 0; - // Would be better 2 not run these serially - while (true) { - // b is a benchmark timer interfacing an unset (NaN) benchmark clock - await func(b); - // Making sure the benchmark was started/stopped properly - assertTiming(clock); - // Summing up - totalMs += clock.stop - clock.start; - // Resetting the benchmark clock - clock.start = clock.stop = NaN; - // Once all ran - if (!--pendingRuns) { - result = `${runs} runs avg: ${totalMs / runs}ms`; - break; - } + + // Calculate length of run + const measuredMs = clock.stop - clock.start; + + // Summing up + totalMs += measuredMs; + // Adding partial result + progress.running.measuredRunsMs.push(measuredMs); + // Publish partial benchmark results + publishProgress(progress, ProgressState.BenchPartialResult, progressCb); + + // Resetting the benchmark clock + clock.start = clock.stop = NaN; + // Once all ran + if (!--pendingRuns) { + result = + runs == 1 + ? `${totalMs}ms` + : `${runs} runs avg: ${totalMs / runs}ms`; + // Adding results + progress.results.push({ + name, + totalMs, + runsCount: runs, + measuredRunsAvgMs: totalMs / runs, + measuredRunsMs: progress.running.measuredRunsMs, + }); + // Clear currently running + delete progress.running; + // Publish results of the benchmark + publishProgress(progress, ProgressState.BenchResult, progressCb); + break; } } } catch (err) { - failed = true; - console.groupEnd(); - console.error(red(err.stack)); + failError = err; + + if (!silent) { + console.groupEnd(); + console.error(red(err.stack)); + } + break; } - // Reporting - console.log(blue(result)); - console.groupEnd(); - measured++; + + if (!silent) { + // Reporting + console.log(blue(result)); + console.groupEnd(); + } + // Resetting the benchmark clock clock.start = clock.stop = NaN; + delete clock.for; } - // Closing results - console.log( - `benchmark result: ${failed ? red("FAIL") : blue("DONE")}. ` + - `${measured} measured; ${filtered} filtered` - ); - // Making sure the program exit code is not zero in case of failure - if (failed) { - setTimeout((): void => exit(1), 0); + + // Indicate finished running + delete progress.queued; + // Publish final result in Cb too + publishProgress(progress, ProgressState.BenchmarkingEnd, progressCb); + + if (!silent) { + // Closing results + console.log( + `benchmark result: ${!!failError ? red("FAIL") : blue("DONE")}. ` + + `${progress.results.length} measured; ${filtered} filtered` + ); } -} -/** Runs specified benchmarks if the enclosing script is main. */ -export function runIfMain( - meta: ImportMeta, - opts: BenchmarkRunOptions = {} -): Promise { - if (meta.main) { - return runBenchmarks(opts); + // Throw error if there was a failing benchmark + if (!!failError) { + throw failError; } - return Promise.resolve(undefined); + + const benchmarkRunResult = { + filtered, + results: progress.results, + }; + + return benchmarkRunResult; +} + +function publishProgress( + progress: BenchmarkRunProgress, + state: ProgressState, + progressCb?: (progress: BenchmarkRunProgress) => void +): void { + progressCb && progressCb(cloneProgressWithState(progress, state)); +} + +function cloneProgressWithState( + progress: BenchmarkRunProgress, + state: ProgressState +): BenchmarkRunProgress { + return deepAssign({}, progress, { state }) as BenchmarkRunProgress; } diff --git a/std/testing/bench_example.ts b/std/testing/bench_example.ts index 401516cca8de19..366521f85c440e 100644 --- a/std/testing/bench_example.ts +++ b/std/testing/bench_example.ts @@ -1,5 +1,5 @@ // https://deno.land/std/testing/bench.ts -import { BenchmarkTimer, bench, runIfMain } from "./bench.ts"; +import { BenchmarkTimer, bench, runBenchmarks } from "./bench.ts"; // Basic bench(function forIncrementX1e9(b: BenchmarkTimer): void { @@ -26,4 +26,6 @@ bench(function throwing(b): void { }); // Bench control -runIfMain(import.meta, { skip: /throw/ }); +if (import.meta.main) { + runBenchmarks({ skip: /throw/ }); +} diff --git a/std/testing/bench_test.ts b/std/testing/bench_test.ts index 6dfc18b1090974..a56d4fd5550cf1 100644 --- a/std/testing/bench_test.ts +++ b/std/testing/bench_test.ts @@ -1,7 +1,18 @@ const { test } = Deno; -import { bench, runBenchmarks } from "./bench.ts"; - -import "./bench_example.ts"; +import { + bench, + runBenchmarks, + BenchmarkRunError, + clearBenchmarks, + BenchmarkRunProgress, + ProgressState, +} from "./bench.ts"; +import { + assertEquals, + assert, + assertThrows, + assertThrowsAsync, +} from "./asserts.ts"; test({ name: "benching", @@ -57,6 +68,283 @@ test({ // Throws bc the timer's stop method is never called }); - await runBenchmarks({ skip: /throw/ }); + const benchResult = await runBenchmarks({ skip: /throw/ }); + + assertEquals(benchResult.filtered, 1); + assertEquals(benchResult.results.length, 5); + + const resultWithSingleRunsFiltered = benchResult.results.filter( + ({ name }) => name === "forDecrementX1e9" + ); + assertEquals(resultWithSingleRunsFiltered.length, 1); + + const resultWithSingleRuns = resultWithSingleRunsFiltered[0]; + assert(!!resultWithSingleRuns.runsCount); + assert(!!resultWithSingleRuns.measuredRunsAvgMs); + assert(!!resultWithSingleRuns.measuredRunsMs); + assertEquals(resultWithSingleRuns.runsCount, 1); + assertEquals(resultWithSingleRuns.measuredRunsMs.length, 1); + + const resultWithMultipleRunsFiltered = benchResult.results.filter( + ({ name }) => name === "runs100ForIncrementX1e6" + ); + assertEquals(resultWithMultipleRunsFiltered.length, 1); + + const resultWithMultipleRuns = resultWithMultipleRunsFiltered[0]; + assert(!!resultWithMultipleRuns.runsCount); + assert(!!resultWithMultipleRuns.measuredRunsAvgMs); + assert(!!resultWithMultipleRuns.measuredRunsMs); + assertEquals(resultWithMultipleRuns.runsCount, 100); + assertEquals(resultWithMultipleRuns.measuredRunsMs.length, 100); + + clearBenchmarks(); + }, +}); + +test({ + name: "Bench without name should throw", + fn() { + assertThrows( + (): void => { + bench(() => {}); + }, + Error, + "The benchmark function must not be anonymous" + ); + }, +}); + +test({ + name: "Bench without stop should throw", + fn: async function (): Promise { + await assertThrowsAsync( + async (): Promise => { + bench(function benchWithoutStop(b): void { + b.start(); + // Throws bc the timer's stop method is never called + }); + await runBenchmarks({ only: /benchWithoutStop/, silent: true }); + }, + BenchmarkRunError, + "The benchmark timer's stop method must be called" + ); + }, +}); + +test({ + name: "Bench without start should throw", + fn: async function (): Promise { + await assertThrowsAsync( + async (): Promise => { + bench(function benchWithoutStart(b): void { + b.stop(); + // Throws bc the timer's start method is never called + }); + await runBenchmarks({ only: /benchWithoutStart/, silent: true }); + }, + BenchmarkRunError, + "The benchmark timer's start method must be called" + ); }, }); + +test({ + name: "Bench with stop before start should throw", + fn: async function (): Promise { + await assertThrowsAsync( + async (): Promise => { + bench(function benchStopBeforeStart(b): void { + b.stop(); + b.start(); + // Throws bc the timer's stop is called before start + }); + await runBenchmarks({ only: /benchStopBeforeStart/, silent: true }); + }, + BenchmarkRunError, + "The benchmark timer's start method must be called before its stop method" + ); + }, +}); + +test({ + name: "clearBenchmarks should clear all candidates", + fn: async function (): Promise { + dummyBench("test"); + + clearBenchmarks(); + const benchingResults = await runBenchmarks({ silent: true }); + + assertEquals(benchingResults.filtered, 0); + assertEquals(benchingResults.results.length, 0); + }, +}); + +test({ + name: "clearBenchmarks with only as option", + fn: async function (): Promise { + // to reset candidates + clearBenchmarks(); + + dummyBench("test"); + dummyBench("onlyclear"); + + clearBenchmarks({ only: /only/ }); + const benchingResults = await runBenchmarks({ silent: true }); + + assertEquals(benchingResults.filtered, 0); + assertEquals(benchingResults.results.length, 1); + assertEquals(benchingResults.results[0].name, "test"); + }, +}); + +test({ + name: "clearBenchmarks with skip as option", + fn: async function (): Promise { + // to reset candidates + clearBenchmarks(); + + dummyBench("test"); + dummyBench("skipclear"); + + clearBenchmarks({ skip: /skip/ }); + const benchingResults = await runBenchmarks({ silent: true }); + + assertEquals(benchingResults.filtered, 0); + assertEquals(benchingResults.results.length, 1); + assertEquals(benchingResults.results[0].name, "skipclear"); + }, +}); + +test({ + name: "clearBenchmarks with only and skip as option", + fn: async function (): Promise { + // to reset candidates + clearBenchmarks(); + + dummyBench("test"); + dummyBench("clearonly"); + dummyBench("clearskip"); + dummyBench("clearonly"); + + clearBenchmarks({ only: /clear/, skip: /skip/ }); + const benchingResults = await runBenchmarks({ silent: true }); + + assertEquals(benchingResults.filtered, 0); + assertEquals(benchingResults.results.length, 2); + assert(!!benchingResults.results.find(({ name }) => name === "test")); + assert(!!benchingResults.results.find(({ name }) => name === "clearskip")); + }, +}); + +test({ + name: "progressCallback of runBenchmarks", + fn: async function (): Promise { + clearBenchmarks(); + dummyBench("skip"); + dummyBench("single"); + dummyBench("multiple", 2); + + const progressCallbacks: BenchmarkRunProgress[] = []; + + const benchingResults = await runBenchmarks( + { skip: /skip/, silent: true }, + (progress) => { + progressCallbacks.push(progress); + } + ); + + let pc = 0; + // Assert initial progress before running + let progress = progressCallbacks[pc++]; + assertEquals(progress.state, ProgressState.BenchmarkingStart); + assertEquals(progress.filtered, 1); + assertEquals(progress.queued.length, 2); + assertEquals(progress.running, undefined); + assertEquals(progress.results, []); + + // Assert start of bench "single" + progress = progressCallbacks[pc++]; + assertEquals(progress.state, ProgressState.BenchStart); + assertEquals(progress.filtered, 1); + assertEquals(progress.queued.length, 1); + assert(!!progress.queued.find(({ name }) => name == "multiple")); + assertEquals(progress.running, { + name: "single", + runsCount: 1, + measuredRunsMs: [], + }); + assertEquals(progress.results, []); + + // Assert running result of bench "single" + progress = progressCallbacks[pc++]; + assertEquals(progress.state, ProgressState.BenchPartialResult); + assertEquals(progress.queued.length, 1); + assertEquals(progress.running!.measuredRunsMs.length, 1); + assertEquals(progress.results.length, 0); + + // Assert result of bench "single" + progress = progressCallbacks[pc++]; + assertEquals(progress.state, ProgressState.BenchResult); + assertEquals(progress.queued.length, 1); + assertEquals(progress.running, undefined); + assertEquals(progress.results.length, 1); + assert(!!progress.results.find(({ name }) => name == "single")); + + // Assert start of bench "multiple" + progress = progressCallbacks[pc++]; + assertEquals(progress.state, ProgressState.BenchStart); + assertEquals(progress.queued.length, 0); + assertEquals(progress.running, { + name: "multiple", + runsCount: 2, + measuredRunsMs: [], + }); + assertEquals(progress.results.length, 1); + + // Assert first result of bench "multiple" + progress = progressCallbacks[pc++]; + assertEquals(progress.state, ProgressState.BenchPartialResult); + assertEquals(progress.queued.length, 0); + assertEquals(progress.running!.measuredRunsMs.length, 1); + assertEquals(progress.results.length, 1); + + // Assert second result of bench "multiple" + progress = progressCallbacks[pc++]; + assertEquals(progress.state, ProgressState.BenchPartialResult); + assertEquals(progress.queued.length, 0); + assertEquals(progress.running!.measuredRunsMs.length, 2); + assertEquals(progress.results.length, 1); + + // Assert finish of bench "multiple" + progress = progressCallbacks[pc++]; + assertEquals(progress.state, ProgressState.BenchResult); + assertEquals(progress.queued.length, 0); + assertEquals(progress.running, undefined); + assertEquals(progress.results.length, 2); + assert(!!progress.results.find(({ name }) => name == "single")); + const resultOfMultiple = progress.results.filter( + ({ name }) => name == "multiple" + ); + assertEquals(resultOfMultiple.length, 1); + assert(!!resultOfMultiple[0].measuredRunsMs); + assert(!isNaN(resultOfMultiple[0].measuredRunsAvgMs)); + assertEquals(resultOfMultiple[0].measuredRunsMs.length, 2); + + // The last progress should equal the final result from promise except the state property + progress = progressCallbacks[pc++]; + assertEquals(progress.state, ProgressState.BenchmarkingEnd); + delete progress.state; + assertEquals(progress, benchingResults); + }, +}); + +function dummyBench(name: string, runs = 1): void { + bench({ + name, + runs, + func(b) { + b.start(); + b.stop(); + }, + }); +} diff --git a/std/uuid/README.md b/std/uuid/README.md index fc2a10e9050f51..2b7c7956545135 100644 --- a/std/uuid/README.md +++ b/std/uuid/README.md @@ -11,5 +11,5 @@ import { v4 } from "https://deno.land/std/uuid/mod.ts"; const myUUID = v4.generate(); // Validate a v4 uuid -const isValid = v4.validate(aString); +const isValid = v4.validate(myUUID); ``` diff --git a/std/version.ts b/std/version.ts index e05359510d31b6..2199cdfed252e4 100644 --- a/std/version.ts +++ b/std/version.ts @@ -4,4 +4,4 @@ * the cli's API is stable. In the future when std becomes stable, likely we * will match versions with cli as we have in the past. */ -export const VERSION = "0.53.0"; +export const VERSION = "0.56.0"; diff --git a/std/ws/README.md b/std/ws/README.md index c166a5fb6b581a..0c3b7a2a27e6dd 100644 --- a/std/ws/README.md +++ b/std/ws/README.md @@ -7,58 +7,62 @@ ws module is made to provide helpers to create WebSocket client/server. ### Server ```ts -import { serve } from "https://deno.land/std/http/server.ts"; +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. +import { serve } from "../../http/server.ts"; import { acceptWebSocket, isWebSocketCloseEvent, isWebSocketPingEvent, -} from "https://deno.land/std/ws/mod.ts"; - -/** websocket echo server */ -const port = Deno.args[0] || "8080"; -console.log(`websocket server is running on :${port}`); -for await (const req of serve(`:${port}`)) { - const { conn, r: bufReader, w: bufWriter, headers } = req; + WebSocket, +} from "../../ws/mod.ts"; +async function handleWs(sock: WebSocket) { + console.log("socket connected!"); try { - const sock = await acceptWebSocket({ + for await (const ev of sock) { + if (typeof ev === "string") { + // text message + console.log("ws:Text", ev); + await sock.send(ev); + } else if (ev instanceof Uint8Array) { + // binary message + console.log("ws:Binary", ev); + } else if (isWebSocketPingEvent(ev)) { + const [, body] = ev; + // ping + console.log("ws:Ping", body); + } else if (isWebSocketCloseEvent(ev)) { + // close + const { code, reason } = ev; + console.log("ws:Close", code, reason); + } + } + } catch (err) { + console.error(`failed to receive frame: ${err}`); + + if (!sock.isClosed) { + await sock.close(1000).catch(console.error); + } + } +} + +if (import.meta.main) { + /** websocket echo server */ + const port = Deno.args[0] || "8080"; + console.log(`websocket server is running on :${port}`); + for await (const req of serve(`:${port}`)) { + const { conn, r: bufReader, w: bufWriter, headers } = req; + acceptWebSocket({ conn, bufReader, bufWriter, headers, - }); - - console.log("socket connected!"); - - try { - for await (const ev of sock) { - if (typeof ev === "string") { - // text message - console.log("ws:Text", ev); - await sock.send(ev); - } else if (ev instanceof Uint8Array) { - // binary message - console.log("ws:Binary", ev); - } else if (isWebSocketPingEvent(ev)) { - const [, body] = ev; - // ping - console.log("ws:Ping", body); - } else if (isWebSocketCloseEvent(ev)) { - // close - const { code, reason } = ev; - console.log("ws:Close", code, reason); - } - } - } catch (err) { - console.error(`failed to receive frame: ${err}`); - - if (!sock.isClosed) { - await sock.close(1000).catch(console.error); - } - } - } catch (err) { - console.error(`failed to accept websocket: ${err}`); - await req.respond({ status: 400 }); + }) + .then(handleWs) + .catch(async (e) => { + console.error(`failed to accept websocket: ${err}`); + await req.respond({ status: 400 }); + }); } } ``` diff --git a/std/ws/example_server.ts b/std/ws/example_server.ts index 3a981595716f08..0d0b2860793b89 100644 --- a/std/ws/example_server.ts +++ b/std/ws/example_server.ts @@ -4,54 +4,55 @@ import { acceptWebSocket, isWebSocketCloseEvent, isWebSocketPingEvent, + WebSocket, } from "./mod.ts"; +async function handleWs(sock: WebSocket): Promise { + console.log("socket connected!"); + try { + for await (const ev of sock) { + if (typeof ev === "string") { + // text message + console.log("ws:Text", ev); + await sock.send(ev); + } else if (ev instanceof Uint8Array) { + // binary message + console.log("ws:Binary", ev); + } else if (isWebSocketPingEvent(ev)) { + const [, body] = ev; + // ping + console.log("ws:Ping", body); + } else if (isWebSocketCloseEvent(ev)) { + // close + const { code, reason } = ev; + console.log("ws:Close", code, reason); + } + } + } catch (err) { + console.error(`failed to receive frame: ${err}`); + + if (!sock.isClosed) { + await sock.close(1000).catch(console.error); + } + } +} + if (import.meta.main) { /** websocket echo server */ const port = Deno.args[0] || "8080"; console.log(`websocket server is running on :${port}`); for await (const req of serve(`:${port}`)) { const { conn, r: bufReader, w: bufWriter, headers } = req; - - try { - const sock = await acceptWebSocket({ - conn, - bufReader, - bufWriter, - headers, + acceptWebSocket({ + conn, + bufReader, + bufWriter, + headers, + }) + .then(handleWs) + .catch(async (e) => { + console.error(`failed to accept websocket: ${e}`); + await req.respond({ status: 400 }); }); - - console.log("socket connected!"); - - try { - for await (const ev of sock) { - if (typeof ev === "string") { - // text message - console.log("ws:Text", ev); - await sock.send(ev); - } else if (ev instanceof Uint8Array) { - // binary message - console.log("ws:Binary", ev); - } else if (isWebSocketPingEvent(ev)) { - const [, body] = ev; - // ping - console.log("ws:Ping", body); - } else if (isWebSocketCloseEvent(ev)) { - // close - const { code, reason } = ev; - console.log("ws:Close", code, reason); - } - } - } catch (err) { - console.error(`failed to receive frame: ${err}`); - - if (!sock.isClosed) { - await sock.close(1000).catch(console.error); - } - } - } catch (err) { - console.error(`failed to accept websocket: ${err}`); - await req.respond({ status: 400 }); - } } } diff --git a/std/ws/example_test.ts b/std/ws/example_test.ts new file mode 100644 index 00000000000000..7e68dc38b4b839 --- /dev/null +++ b/std/ws/example_test.ts @@ -0,0 +1,2 @@ +import "./example_client.ts"; +import "./example_server.ts"; diff --git a/std/ws/mod.ts b/std/ws/mod.ts index 97c77baab024d8..f98e70d928272e 100644 --- a/std/ws/mod.ts +++ b/std/ws/mod.ts @@ -491,7 +491,7 @@ export async function handshake( throw new Error("ws: invalid status line: " + statusLine); } - // @ts-expect-error + assert(m.groups); const { version, statusCode } = m.groups; if (version !== "HTTP/1.1" || statusCode !== "101") { throw new Error( diff --git a/std/ws/test.ts b/std/ws/test.ts index a1c396b186c52c..9ef6ff94b363ec 100644 --- a/std/ws/test.ts +++ b/std/ws/test.ts @@ -283,8 +283,8 @@ function dummyConn(r: Reader, w: Writer): Conn { return { rid: -1, closeWrite: (): void => {}, - read: (x): Promise => r.read(x), - write: (x): Promise => w.write(x), + read: (x: Uint8Array): Promise => r.read(x), + write: (x: Uint8Array): Promise => w.write(x), close: (): void => {}, localAddr: { transport: "tcp", hostname: "0.0.0.0", port: 0 }, remoteAddr: { transport: "tcp", hostname: "0.0.0.0", port: 0 }, diff --git a/test_plugin/Cargo.toml b/test_plugin/Cargo.toml index 704cee8314fc4c..3787579a8f1309 100644 --- a/test_plugin/Cargo.toml +++ b/test_plugin/Cargo.toml @@ -9,5 +9,5 @@ publish = false crate-type = ["cdylib"] [dependencies] -futures = "0.3.4" +futures = "0.3.5" deno_core = { path = "../core" } diff --git a/test_plugin/src/lib.rs b/test_plugin/src/lib.rs index 37868b31026580..781bc42594fda3 100644 --- a/test_plugin/src/lib.rs +++ b/test_plugin/src/lib.rs @@ -13,15 +13,16 @@ pub fn deno_plugin_init(interface: &mut dyn Interface) { fn op_test_sync( _interface: &mut dyn Interface, data: &[u8], - zero_copy: Option, + zero_copy: &mut [ZeroCopyBuf], ) -> Op { - if let Some(buf) = zero_copy { - let data_str = std::str::from_utf8(&data[..]).unwrap(); + let data_str = std::str::from_utf8(&data[..]).unwrap(); + let zero_copy = zero_copy.to_vec(); + if !zero_copy.is_empty() { + println!("Hello from plugin. data: {}", data_str); + } + for (idx, buf) in zero_copy.iter().enumerate() { let buf_str = std::str::from_utf8(&buf[..]).unwrap(); - println!( - "Hello from plugin. data: {} | zero_copy: {}", - data_str, buf_str - ); + println!("zero_copy[{}]: {}", idx, buf_str); } let result = b"test"; let result_box: Buf = Box::new(*result); @@ -31,16 +32,17 @@ fn op_test_sync( fn op_test_async( _interface: &mut dyn Interface, data: &[u8], - zero_copy: Option, + zero_copy: &mut [ZeroCopyBuf], ) -> Op { - let data_str = std::str::from_utf8(&data[..]).unwrap().to_string(); + let zero_copy = zero_copy.to_vec(); + if !zero_copy.is_empty() { + let data_str = std::str::from_utf8(&data[..]).unwrap().to_string(); + println!("Hello from plugin. data: {}", data_str); + } let fut = async move { - if let Some(buf) = zero_copy { + for (idx, buf) in zero_copy.iter().enumerate() { let buf_str = std::str::from_utf8(&buf[..]).unwrap(); - println!( - "Hello from plugin. data: {} | zero_copy: {}", - data_str, buf_str - ); + println!("zero_copy[{}]: {}", idx, buf_str); } let (tx, rx) = futures::channel::oneshot::channel::>(); std::thread::spawn(move || { diff --git a/test_plugin/tests/integration_tests.rs b/test_plugin/tests/integration_tests.rs index 17002fc0142b47..8716048b1244a6 100644 --- a/test_plugin/tests/integration_tests.rs +++ b/test_plugin/tests/integration_tests.rs @@ -57,7 +57,7 @@ fn basic() { println!("stderr {}", stderr); } assert!(output.status.success()); - let expected = "Hello from plugin. data: test | zero_copy: test\nPlugin Sync Response: test\nHello from plugin. data: test | zero_copy: test\nPlugin Async Response: test\n"; + let expected = "Hello from plugin. data: test\nzero_copy[0]: test\nzero_copy[1]: 123\nzero_copy[2]: cba\nPlugin Sync Response: test\nHello from plugin. data: test\nzero_copy[0]: test\nzero_copy[1]: 123\nPlugin Async Response: test\n"; assert_eq!(stdout, expected); assert_eq!(stderr, ""); } diff --git a/test_plugin/tests/test.js b/test_plugin/tests/test.js index 8d6146902d5509..fbe58aeb8138af 100644 --- a/test_plugin/tests/test.js +++ b/test_plugin/tests/test.js @@ -33,7 +33,9 @@ function runTestSync() { const response = Deno.core.dispatch( testSync, new Uint8Array([116, 101, 115, 116]), - new Uint8Array([116, 101, 115, 116]) + new Uint8Array([116, 101, 115, 116]), + new Uint8Array([49, 50, 51]), + new Uint8Array([99, 98, 97]) ); console.log(`Plugin Sync Response: ${textDecoder.decode(response)}`); @@ -47,7 +49,8 @@ function runTestAsync() { const response = Deno.core.dispatch( testAsync, new Uint8Array([116, 101, 115, 116]), - new Uint8Array([116, 101, 115, 116]) + new Uint8Array([116, 101, 115, 116]), + new Uint8Array([49, 50, 51]) ); if (response != null || response != undefined) { @@ -80,9 +83,11 @@ function runTestPluginClose() { const preStr = JSON.stringify(resourcesPre, null, 2); const postStr = JSON.stringify(resourcesPost, null, 2); if (preStr !== postStr) { - throw new Error(`Difference in open resources before openPlugin and after Plugin.close(): + throw new Error( + `Difference in open resources before openPlugin and after Plugin.close(): Before: ${preStr} -After: ${postStr}`); +After: ${postStr}` + ); } } diff --git a/tools/http_server.py b/tools/http_server.py index 346b319f801377..d143f0ba8e2c48 100755 --- a/tools/http_server.py +++ b/tools/http_server.py @@ -194,8 +194,9 @@ def do_GET(self): def do_POST(self): # Simple echo server for request reflection if "echo_server" in self.path: + status = int(self.headers.getheader('x-status', "200")) self.protocol_version = 'HTTP/1.1' - self.send_response(200, 'OK') + self.send_response(status, 'OK') if self.headers.has_key('content-type'): self.send_header('content-type', self.headers.getheader('content-type')) @@ -206,6 +207,25 @@ def do_POST(self): data_string = self.rfile.read(int(self.headers['Content-Length'])) self.wfile.write(bytes(data_string)) return + if "echo_multipart_file" in self.path: + self.protocol_version = 'HTTP/1.1' + self.send_response(200, 'OK') + self.send_header('Content-type', + 'multipart/form-data;boundary=boundary') + self.end_headers() + file_content = self.rfile.read(int(self.headers['Content-Length'])) + self.wfile.write( + bytes('--boundary\t \r\n' + 'Content-Disposition: form-data; name="field_1"\r\n' + '\r\n' + 'value_1 \r\n' + '\r\n--boundary\r\n' + 'Content-Disposition: form-data; name="file"; ' + 'filename="file.bin"\r\n' + 'Content-Type: application/octet-stream\r\n' + '\r\n') + bytes(file_content) + + bytes('\r\n--boundary--\r\n')) + return self.protocol_version = 'HTTP/1.1' self.send_response(501) self.send_header('content-type', 'text/plain') @@ -363,7 +383,7 @@ def start(s): def spawn(): servers = (server(), redirect_server(), another_redirect_server(), double_redirects_server(), https_server(), - absolute_redirect_server()) + absolute_redirect_server(), inf_redirects_server()) # In order to wait for each of the servers to be ready, we try connecting to # them with a tcp socket. for running_server in servers: diff --git a/tools/hyper_hello/Cargo.toml b/tools/hyper_hello/Cargo.toml index 9029815ccb6956..55a8be7b10d1ee 100644 --- a/tools/hyper_hello/Cargo.toml +++ b/tools/hyper_hello/Cargo.toml @@ -4,8 +4,8 @@ version = "0.0.1" edition = "2018" [dependencies] -hyper = "0.13.5" -tokio = { version = "0.2.20", features = ["full"] } +hyper = "0.13.6" +tokio = { version = "0.2.21", features = ["full"] } [[bin]] name = "hyper_hello"