mirror of
https://github.com/denoland/deno.git
synced 2024-12-22 07:14:47 -05:00
refactor(core): Extract deno_core (#19658)
`deno_core` is moving out! You'll find it at https://github.com/denoland/deno_core/ once this PR lands.
This commit is contained in:
parent
b9c0e7cd55
commit
e746b6d806
223 changed files with 17 additions and 36602 deletions
77
Cargo.lock
generated
77
Cargo.lock
generated
|
@ -357,15 +357,6 @@ version = "1.6.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
|
||||
|
||||
[[package]]
|
||||
name = "basic-toml"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c0de75129aa8d0cceaf750b89013f0e08804d6ec61416da787b35ad0d7cddf1"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bencher"
|
||||
version = "0.1.5"
|
||||
|
@ -607,12 +598,6 @@ version = "0.4.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
|
||||
|
||||
[[package]]
|
||||
name = "cooked-waker"
|
||||
version = "5.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "147be55d677052dabc6b22252d5dd0fd4c29c8c27aa4f2fbef0f94aa003b406f"
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation"
|
||||
version = "0.9.3"
|
||||
|
@ -973,12 +958,12 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "deno_core"
|
||||
version = "0.191.0"
|
||||
version = "0.193.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2d7eeb863655f377ffe22d5a90f01d1b57b4d4ad0acbfeec266d5d3faf4cd1cf"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
"cooked-waker",
|
||||
"deno_ast",
|
||||
"deno_ops",
|
||||
"futures",
|
||||
"indexmap",
|
||||
|
@ -1346,14 +1331,14 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "deno_ops"
|
||||
version = "0.69.0"
|
||||
version = "0.71.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2dd95626e113f292ce758f216bb12e50c9c58e886195bcd85231e715c73d6470"
|
||||
dependencies = [
|
||||
"deno-proc-macro-rules",
|
||||
"lazy-regex",
|
||||
"once_cell",
|
||||
"pmutil",
|
||||
"pretty_assertions",
|
||||
"prettyplease",
|
||||
"proc-macro-crate",
|
||||
"proc-macro2 1.0.60",
|
||||
"quote 1.0.28",
|
||||
|
@ -1362,9 +1347,7 @@ dependencies = [
|
|||
"strum_macros",
|
||||
"syn 1.0.109",
|
||||
"syn 2.0.18",
|
||||
"testing_macros",
|
||||
"thiserror",
|
||||
"trybuild",
|
||||
"v8",
|
||||
]
|
||||
|
||||
|
@ -3679,16 +3662,6 @@ dependencies = [
|
|||
"yansi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "prettyplease"
|
||||
version = "0.1.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c8646e95016a7a6c4adea95bafa8a16baab64b583356217f2c85db4a39d9a86"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.60",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "primeorder"
|
||||
version = "0.13.1"
|
||||
|
@ -4393,15 +4366,15 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "serde_v8"
|
||||
version = "0.102.0"
|
||||
version = "0.104.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2481189a5019f43a6b68620193578623701323754409555c36588c81ee2487de"
|
||||
dependencies = [
|
||||
"bencher",
|
||||
"bytes",
|
||||
"derive_more",
|
||||
"num-bigint",
|
||||
"serde",
|
||||
"serde_bytes",
|
||||
"serde_json",
|
||||
"smallvec",
|
||||
"thiserror",
|
||||
"v8",
|
||||
|
@ -5244,23 +5217,6 @@ dependencies = [
|
|||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "testing_macros"
|
||||
version = "0.2.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6d536c776d47c59f8f47fbf4e7062b23095be9fce218d11d9c9fb988b579dfa"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"glob",
|
||||
"once_cell",
|
||||
"pmutil",
|
||||
"proc-macro2 1.0.60",
|
||||
"quote 1.0.28",
|
||||
"regex",
|
||||
"relative-path",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "text-size"
|
||||
version = "1.1.0"
|
||||
|
@ -5650,21 +5606,6 @@ version = "0.2.4"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed"
|
||||
|
||||
[[package]]
|
||||
name = "trybuild"
|
||||
version = "1.0.79"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "db3115bddce1b5f52dd4b5e0ec8298a66ce733e4cc6759247dc2d1c11508ec38"
|
||||
dependencies = [
|
||||
"basic-toml",
|
||||
"glob",
|
||||
"once_cell",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"termcolor",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "twox-hash"
|
||||
version = "1.6.3"
|
||||
|
|
12
Cargo.toml
12
Cargo.toml
|
@ -6,10 +6,7 @@ members = [
|
|||
"bench_util",
|
||||
"cli",
|
||||
"cli/napi/sym",
|
||||
"core",
|
||||
"ops",
|
||||
"runtime",
|
||||
"serde_v8",
|
||||
"test_ffi",
|
||||
"test_napi",
|
||||
"test_util",
|
||||
|
@ -44,9 +41,10 @@ repository = "https://github.com/denoland/deno"
|
|||
v8 = { version = "0.74.1", default-features = false }
|
||||
deno_ast = { version = "0.27.0", features = ["transpiling"] }
|
||||
|
||||
deno_core = { version = "0.191.0", path = "./core" }
|
||||
deno_ops = { version = "0.69.0", path = "./ops" }
|
||||
serde_v8 = { version = "0.102.0", path = "./serde_v8" }
|
||||
deno_core = { version = "0.193.0" }
|
||||
deno_ops = { version = "0.70.0" }
|
||||
serde_v8 = { version = "0.103.0" }
|
||||
|
||||
deno_runtime = { version = "0.117.0", path = "./runtime" }
|
||||
napi_sym = { version = "0.39.0", path = "./cli/napi/sym" }
|
||||
deno_bench_util = { version = "0.103.0", path = "./bench_util" }
|
||||
|
@ -132,8 +130,6 @@ signature = "=1.6.4"
|
|||
slab = "0.4"
|
||||
smallvec = "1.8"
|
||||
socket2 = "0.4.7"
|
||||
strum = { version = "0.24", features = ["derive"] }
|
||||
strum_macros = "0.24"
|
||||
tar = "=0.4.38"
|
||||
tempfile = "3.4.0"
|
||||
thiserror = "1.0.40"
|
||||
|
|
|
@ -194,35 +194,6 @@ fn typecheck_declarations_unstable() {
|
|||
output.assert_exit_code(0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn typecheck_core() {
|
||||
let context = TestContext::default();
|
||||
let deno_dir = context.deno_dir();
|
||||
let test_file = deno_dir.path().join("test_deno_core_types.ts");
|
||||
std::fs::write(
|
||||
&test_file,
|
||||
format!(
|
||||
"import \"{}\";",
|
||||
deno_core::resolve_path(
|
||||
&util::root_path()
|
||||
.join("core")
|
||||
.join("lib.deno_core.d.ts")
|
||||
.to_string(),
|
||||
&std::env::current_dir().unwrap()
|
||||
)
|
||||
.unwrap()
|
||||
),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let args = vec!["run".to_string(), test_file.to_string()];
|
||||
let output = context.new_command().args_vec(args).split_output().run();
|
||||
|
||||
println!("stdout: {}", output.stdout());
|
||||
println!("stderr: {}", output.stderr());
|
||||
output.assert_exit_code(0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ts_no_recheck_on_redirect() {
|
||||
let test_context = TestContext::default();
|
||||
|
|
|
@ -135,11 +135,11 @@ Deno.test({ permissions: { read: true } }, function lstatSyncSuccess() {
|
|||
assert(!modulesInfoByUrl.isDirectory);
|
||||
assert(modulesInfoByUrl.isSymlink);
|
||||
|
||||
const coreInfo = Deno.lstatSync("core");
|
||||
const coreInfo = Deno.lstatSync("cli");
|
||||
assert(coreInfo.isDirectory);
|
||||
assert(!coreInfo.isSymlink);
|
||||
|
||||
const coreInfoByUrl = Deno.lstatSync(pathToAbsoluteFileUrl("core"));
|
||||
const coreInfoByUrl = Deno.lstatSync(pathToAbsoluteFileUrl("cli"));
|
||||
assert(coreInfoByUrl.isDirectory);
|
||||
assert(!coreInfoByUrl.isSymlink);
|
||||
});
|
||||
|
@ -261,11 +261,11 @@ Deno.test({ permissions: { read: true } }, async function lstatSuccess() {
|
|||
assert(!modulesInfoByUrl.isDirectory);
|
||||
assert(modulesInfoByUrl.isSymlink);
|
||||
|
||||
const coreInfo = await Deno.lstat("core");
|
||||
const coreInfo = await Deno.lstat("cli");
|
||||
assert(coreInfo.isDirectory);
|
||||
assert(!coreInfo.isSymlink);
|
||||
|
||||
const coreInfoByUrl = await Deno.lstat(pathToAbsoluteFileUrl("core"));
|
||||
const coreInfoByUrl = await Deno.lstat(pathToAbsoluteFileUrl("cli"));
|
||||
assert(coreInfoByUrl.isDirectory);
|
||||
assert(!coreInfoByUrl.isSymlink);
|
||||
});
|
||||
|
|
|
@ -1,615 +0,0 @@
|
|||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
||||
|
||||
// Based on https://github.com/nodejs/node/blob/889ad35d3d41e376870f785b0c1b669cb732013d/lib/internal/per_context/primordials.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.
|
||||
// This file subclasses and stores the JS builtins that come from the VM
|
||||
// so that Node.js's builtin modules do not need to later look these up from
|
||||
// the global proxy, which can be mutated by users.
|
||||
|
||||
// Use of primordials have sometimes a dramatic impact on performance, please
|
||||
// benchmark all changes made in performance-sensitive areas of the codebase.
|
||||
// See: https://github.com/nodejs/node/pull/38248
|
||||
|
||||
// deno-lint-ignore-file prefer-primordials
|
||||
|
||||
"use strict";
|
||||
|
||||
(() => {
|
||||
const primordials = {};
|
||||
|
||||
const {
|
||||
defineProperty: ReflectDefineProperty,
|
||||
getOwnPropertyDescriptor: ReflectGetOwnPropertyDescriptor,
|
||||
ownKeys: ReflectOwnKeys,
|
||||
} = Reflect;
|
||||
|
||||
// `uncurryThis` is equivalent to `func => Function.prototype.call.bind(func)`.
|
||||
// It is using `bind.bind(call)` to avoid using `Function.prototype.bind`
|
||||
// and `Function.prototype.call` after it may have been mutated by users.
|
||||
const { apply, bind, call } = Function.prototype;
|
||||
const uncurryThis = bind.bind(call);
|
||||
primordials.uncurryThis = uncurryThis;
|
||||
|
||||
// `applyBind` is equivalent to `func => Function.prototype.apply.bind(func)`.
|
||||
// It is using `bind.bind(apply)` to avoid using `Function.prototype.bind`
|
||||
// and `Function.prototype.apply` after it may have been mutated by users.
|
||||
const applyBind = bind.bind(apply);
|
||||
primordials.applyBind = applyBind;
|
||||
|
||||
// Methods that accept a variable number of arguments, and thus it's useful to
|
||||
// also create `${prefix}${key}Apply`, which uses `Function.prototype.apply`,
|
||||
// instead of `Function.prototype.call`, and thus doesn't require iterator
|
||||
// destructuring.
|
||||
const varargsMethods = [
|
||||
// 'ArrayPrototypeConcat' is omitted, because it performs the spread
|
||||
// on its own for arrays and array-likes with a truthy
|
||||
// @@isConcatSpreadable symbol property.
|
||||
"ArrayOf",
|
||||
"ArrayPrototypePush",
|
||||
"ArrayPrototypeUnshift",
|
||||
// 'FunctionPrototypeCall' is omitted, since there's 'ReflectApply'
|
||||
// and 'FunctionPrototypeApply'.
|
||||
"MathHypot",
|
||||
"MathMax",
|
||||
"MathMin",
|
||||
"StringPrototypeConcat",
|
||||
"TypedArrayOf",
|
||||
];
|
||||
|
||||
function getNewKey(key) {
|
||||
return typeof key === "symbol"
|
||||
? `Symbol${key.description[7].toUpperCase()}${key.description.slice(8)}`
|
||||
: `${key[0].toUpperCase()}${key.slice(1)}`;
|
||||
}
|
||||
|
||||
function copyAccessor(dest, prefix, key, { enumerable, get, set }) {
|
||||
ReflectDefineProperty(dest, `${prefix}Get${key}`, {
|
||||
value: uncurryThis(get),
|
||||
enumerable,
|
||||
});
|
||||
if (set !== undefined) {
|
||||
ReflectDefineProperty(dest, `${prefix}Set${key}`, {
|
||||
value: uncurryThis(set),
|
||||
enumerable,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function copyPropsRenamed(src, dest, prefix) {
|
||||
for (const key of ReflectOwnKeys(src)) {
|
||||
const newKey = getNewKey(key);
|
||||
const desc = ReflectGetOwnPropertyDescriptor(src, key);
|
||||
if ("get" in desc) {
|
||||
copyAccessor(dest, prefix, newKey, desc);
|
||||
} else {
|
||||
const name = `${prefix}${newKey}`;
|
||||
ReflectDefineProperty(dest, name, desc);
|
||||
if (varargsMethods.includes(name)) {
|
||||
ReflectDefineProperty(dest, `${name}Apply`, {
|
||||
// `src` is bound as the `this` so that the static `this` points
|
||||
// to the object it was defined on,
|
||||
// e.g.: `ArrayOfApply` gets a `this` of `Array`:
|
||||
value: applyBind(desc.value, src),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function copyPropsRenamedBound(src, dest, prefix) {
|
||||
for (const key of ReflectOwnKeys(src)) {
|
||||
const newKey = getNewKey(key);
|
||||
const desc = ReflectGetOwnPropertyDescriptor(src, key);
|
||||
if ("get" in desc) {
|
||||
copyAccessor(dest, prefix, newKey, desc);
|
||||
} else {
|
||||
const { value } = desc;
|
||||
if (typeof value === "function") {
|
||||
desc.value = value.bind(src);
|
||||
}
|
||||
|
||||
const name = `${prefix}${newKey}`;
|
||||
ReflectDefineProperty(dest, name, desc);
|
||||
if (varargsMethods.includes(name)) {
|
||||
ReflectDefineProperty(dest, `${name}Apply`, {
|
||||
value: applyBind(value, src),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function copyPrototype(src, dest, prefix) {
|
||||
for (const key of ReflectOwnKeys(src)) {
|
||||
const newKey = getNewKey(key);
|
||||
const desc = ReflectGetOwnPropertyDescriptor(src, key);
|
||||
if ("get" in desc) {
|
||||
copyAccessor(dest, prefix, newKey, desc);
|
||||
} else {
|
||||
const { value } = desc;
|
||||
if (typeof value === "function") {
|
||||
desc.value = uncurryThis(value);
|
||||
}
|
||||
|
||||
const name = `${prefix}${newKey}`;
|
||||
ReflectDefineProperty(dest, name, desc);
|
||||
if (varargsMethods.includes(name)) {
|
||||
ReflectDefineProperty(dest, `${name}Apply`, {
|
||||
value: applyBind(value),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create copies of configurable value properties of the global object
|
||||
[
|
||||
"Proxy",
|
||||
"globalThis",
|
||||
].forEach((name) => {
|
||||
primordials[name] = globalThis[name];
|
||||
});
|
||||
|
||||
// Create copy of isNaN
|
||||
primordials[isNaN.name] = isNaN;
|
||||
|
||||
// Create copies of URI handling functions
|
||||
[
|
||||
decodeURI,
|
||||
decodeURIComponent,
|
||||
encodeURI,
|
||||
encodeURIComponent,
|
||||
].forEach((fn) => {
|
||||
primordials[fn.name] = fn;
|
||||
});
|
||||
|
||||
// Create copies of the namespace objects
|
||||
[
|
||||
"JSON",
|
||||
"Math",
|
||||
"Proxy",
|
||||
"Reflect",
|
||||
].forEach((name) => {
|
||||
copyPropsRenamed(globalThis[name], primordials, name);
|
||||
});
|
||||
|
||||
// Create copies of intrinsic objects
|
||||
[
|
||||
"AggregateError",
|
||||
"Array",
|
||||
"ArrayBuffer",
|
||||
"BigInt",
|
||||
"BigInt64Array",
|
||||
"BigUint64Array",
|
||||
"Boolean",
|
||||
"DataView",
|
||||
"Date",
|
||||
"Error",
|
||||
"EvalError",
|
||||
"FinalizationRegistry",
|
||||
"Float32Array",
|
||||
"Float64Array",
|
||||
"Function",
|
||||
"Int16Array",
|
||||
"Int32Array",
|
||||
"Int8Array",
|
||||
"Map",
|
||||
"Number",
|
||||
"Object",
|
||||
"RangeError",
|
||||
"ReferenceError",
|
||||
"RegExp",
|
||||
"Set",
|
||||
"String",
|
||||
"Symbol",
|
||||
"SyntaxError",
|
||||
"TypeError",
|
||||
"URIError",
|
||||
"Uint16Array",
|
||||
"Uint32Array",
|
||||
"Uint8Array",
|
||||
"Uint8ClampedArray",
|
||||
"WeakMap",
|
||||
"WeakRef",
|
||||
"WeakSet",
|
||||
].forEach((name) => {
|
||||
const original = globalThis[name];
|
||||
primordials[name] = original;
|
||||
copyPropsRenamed(original, primordials, name);
|
||||
copyPrototype(original.prototype, primordials, `${name}Prototype`);
|
||||
});
|
||||
|
||||
// Create copies of intrinsic objects that require a valid `this` to call
|
||||
// static methods.
|
||||
// Refs: https://www.ecma-international.org/ecma-262/#sec-promise.all
|
||||
[
|
||||
"Promise",
|
||||
].forEach((name) => {
|
||||
const original = globalThis[name];
|
||||
primordials[name] = original;
|
||||
copyPropsRenamedBound(original, primordials, name);
|
||||
copyPrototype(original.prototype, primordials, `${name}Prototype`);
|
||||
});
|
||||
|
||||
// Create copies of abstract intrinsic objects that are not directly exposed
|
||||
// on the global object.
|
||||
// Refs: https://tc39.es/ecma262/#sec-%typedarray%-intrinsic-object
|
||||
[
|
||||
{ name: "TypedArray", original: Reflect.getPrototypeOf(Uint8Array) },
|
||||
{
|
||||
name: "ArrayIterator",
|
||||
original: {
|
||||
prototype: Reflect.getPrototypeOf(Array.prototype[Symbol.iterator]()),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "SetIterator",
|
||||
original: {
|
||||
prototype: Reflect.getPrototypeOf(new Set()[Symbol.iterator]()),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "MapIterator",
|
||||
original: {
|
||||
prototype: Reflect.getPrototypeOf(new Map()[Symbol.iterator]()),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "StringIterator",
|
||||
original: {
|
||||
prototype: Reflect.getPrototypeOf(String.prototype[Symbol.iterator]()),
|
||||
},
|
||||
},
|
||||
{ name: "Generator", original: Reflect.getPrototypeOf(function* () {}) },
|
||||
{
|
||||
name: "AsyncGenerator",
|
||||
original: Reflect.getPrototypeOf(async function* () {}),
|
||||
},
|
||||
].forEach(({ name, original }) => {
|
||||
primordials[name] = original;
|
||||
// The static %TypedArray% methods require a valid `this`, but can't be bound,
|
||||
// as they need a subclass constructor as the receiver:
|
||||
copyPrototype(original, primordials, name);
|
||||
copyPrototype(original.prototype, primordials, `${name}Prototype`);
|
||||
});
|
||||
|
||||
const {
|
||||
ArrayPrototypeForEach,
|
||||
ArrayPrototypeJoin,
|
||||
ArrayPrototypeMap,
|
||||
FunctionPrototypeCall,
|
||||
ObjectDefineProperty,
|
||||
ObjectFreeze,
|
||||
ObjectPrototypeIsPrototypeOf,
|
||||
ObjectSetPrototypeOf,
|
||||
Promise,
|
||||
PromisePrototype,
|
||||
PromisePrototypeThen,
|
||||
SymbolIterator,
|
||||
TypedArrayPrototypeJoin,
|
||||
} = primordials;
|
||||
|
||||
// Because these functions are used by `makeSafe`, which is exposed
|
||||
// on the `primordials` object, it's important to use const references
|
||||
// to the primordials that they use:
|
||||
const createSafeIterator = (factory, next) => {
|
||||
class SafeIterator {
|
||||
constructor(iterable) {
|
||||
this._iterator = factory(iterable);
|
||||
}
|
||||
next() {
|
||||
return next(this._iterator);
|
||||
}
|
||||
[SymbolIterator]() {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
ObjectSetPrototypeOf(SafeIterator.prototype, null);
|
||||
ObjectFreeze(SafeIterator.prototype);
|
||||
ObjectFreeze(SafeIterator);
|
||||
return SafeIterator;
|
||||
};
|
||||
|
||||
const SafeArrayIterator = createSafeIterator(
|
||||
primordials.ArrayPrototypeSymbolIterator,
|
||||
primordials.ArrayIteratorPrototypeNext,
|
||||
);
|
||||
primordials.SafeArrayIterator = SafeArrayIterator;
|
||||
primordials.SafeSetIterator = createSafeIterator(
|
||||
primordials.SetPrototypeSymbolIterator,
|
||||
primordials.SetIteratorPrototypeNext,
|
||||
);
|
||||
primordials.SafeMapIterator = createSafeIterator(
|
||||
primordials.MapPrototypeSymbolIterator,
|
||||
primordials.MapIteratorPrototypeNext,
|
||||
);
|
||||
primordials.SafeStringIterator = createSafeIterator(
|
||||
primordials.StringPrototypeSymbolIterator,
|
||||
primordials.StringIteratorPrototypeNext,
|
||||
);
|
||||
|
||||
const copyProps = (src, dest) => {
|
||||
ArrayPrototypeForEach(ReflectOwnKeys(src), (key) => {
|
||||
if (!ReflectGetOwnPropertyDescriptor(dest, key)) {
|
||||
ReflectDefineProperty(
|
||||
dest,
|
||||
key,
|
||||
ReflectGetOwnPropertyDescriptor(src, key),
|
||||
);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @type {typeof primordials.makeSafe}
|
||||
*/
|
||||
const makeSafe = (unsafe, safe) => {
|
||||
if (SymbolIterator in unsafe.prototype) {
|
||||
const dummy = new unsafe();
|
||||
let next; // We can reuse the same `next` method.
|
||||
|
||||
ArrayPrototypeForEach(ReflectOwnKeys(unsafe.prototype), (key) => {
|
||||
if (!ReflectGetOwnPropertyDescriptor(safe.prototype, key)) {
|
||||
const desc = ReflectGetOwnPropertyDescriptor(unsafe.prototype, key);
|
||||
if (
|
||||
typeof desc.value === "function" &&
|
||||
desc.value.length === 0 &&
|
||||
SymbolIterator in (FunctionPrototypeCall(desc.value, dummy) ?? {})
|
||||
) {
|
||||
const createIterator = uncurryThis(desc.value);
|
||||
next ??= uncurryThis(createIterator(dummy).next);
|
||||
const SafeIterator = createSafeIterator(createIterator, next);
|
||||
desc.value = function () {
|
||||
return new SafeIterator(this);
|
||||
};
|
||||
}
|
||||
ReflectDefineProperty(safe.prototype, key, desc);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
copyProps(unsafe.prototype, safe.prototype);
|
||||
}
|
||||
copyProps(unsafe, safe);
|
||||
|
||||
ObjectSetPrototypeOf(safe.prototype, null);
|
||||
ObjectFreeze(safe.prototype);
|
||||
ObjectFreeze(safe);
|
||||
return safe;
|
||||
};
|
||||
primordials.makeSafe = makeSafe;
|
||||
|
||||
// Subclass the constructors because we need to use their prototype
|
||||
// methods later.
|
||||
// Defining the `constructor` is necessary here to avoid the default
|
||||
// constructor which uses the user-mutable `%ArrayIteratorPrototype%.next`.
|
||||
primordials.SafeMap = makeSafe(
|
||||
Map,
|
||||
class SafeMap extends Map {
|
||||
constructor(i) {
|
||||
if (i == null) {
|
||||
super();
|
||||
return;
|
||||
}
|
||||
super(new SafeArrayIterator(i));
|
||||
}
|
||||
},
|
||||
);
|
||||
primordials.SafeWeakMap = makeSafe(
|
||||
WeakMap,
|
||||
class SafeWeakMap extends WeakMap {
|
||||
constructor(i) {
|
||||
if (i == null) {
|
||||
super();
|
||||
return;
|
||||
}
|
||||
super(new SafeArrayIterator(i));
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
primordials.SafeSet = makeSafe(
|
||||
Set,
|
||||
class SafeSet extends Set {
|
||||
constructor(i) {
|
||||
if (i == null) {
|
||||
super();
|
||||
return;
|
||||
}
|
||||
super(new SafeArrayIterator(i));
|
||||
}
|
||||
},
|
||||
);
|
||||
primordials.SafeWeakSet = makeSafe(
|
||||
WeakSet,
|
||||
class SafeWeakSet extends WeakSet {
|
||||
constructor(i) {
|
||||
if (i == null) {
|
||||
super();
|
||||
return;
|
||||
}
|
||||
super(new SafeArrayIterator(i));
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
primordials.SafeRegExp = makeSafe(
|
||||
RegExp,
|
||||
class SafeRegExp extends RegExp {
|
||||
constructor(pattern, flags) {
|
||||
super(pattern, flags);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
primordials.SafeFinalizationRegistry = makeSafe(
|
||||
FinalizationRegistry,
|
||||
class SafeFinalizationRegistry extends FinalizationRegistry {
|
||||
constructor(cleanupCallback) {
|
||||
super(cleanupCallback);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
primordials.SafeWeakRef = makeSafe(
|
||||
WeakRef,
|
||||
class SafeWeakRef extends WeakRef {
|
||||
constructor(target) {
|
||||
super(target);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
const SafePromise = makeSafe(
|
||||
Promise,
|
||||
class SafePromise extends Promise {
|
||||
constructor(executor) {
|
||||
super(executor);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
primordials.ArrayPrototypeToString = (thisArray) =>
|
||||
ArrayPrototypeJoin(thisArray);
|
||||
|
||||
primordials.TypedArrayPrototypeToString = (thisArray) =>
|
||||
TypedArrayPrototypeJoin(thisArray);
|
||||
|
||||
primordials.PromisePrototypeCatch = (thisPromise, onRejected) =>
|
||||
PromisePrototypeThen(thisPromise, undefined, onRejected);
|
||||
|
||||
const arrayToSafePromiseIterable = (array) =>
|
||||
new SafeArrayIterator(
|
||||
ArrayPrototypeMap(
|
||||
array,
|
||||
(p) => {
|
||||
if (ObjectPrototypeIsPrototypeOf(PromisePrototype, p)) {
|
||||
return new SafePromise((c, d) => PromisePrototypeThen(p, c, d));
|
||||
}
|
||||
return p;
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
/**
|
||||
* Creates a Promise that is resolved with an array of results when all of the
|
||||
* provided Promises resolve, or rejected when any Promise is rejected.
|
||||
* @template T
|
||||
* @param {Array<T | PromiseLike<T>>} values
|
||||
* @returns {Promise<Awaited<T>[]>}
|
||||
*/
|
||||
primordials.SafePromiseAll = (values) =>
|
||||
// Wrapping on a new Promise is necessary to not expose the SafePromise
|
||||
// prototype to user-land.
|
||||
new Promise((a, b) =>
|
||||
SafePromise.all(arrayToSafePromiseIterable(values)).then(a, b)
|
||||
);
|
||||
|
||||
// NOTE: Uncomment the following functions when you need to use them
|
||||
|
||||
// /**
|
||||
// * Creates a Promise that is resolved with an array of results when all
|
||||
// * of the provided Promises resolve or reject.
|
||||
// * @template T
|
||||
// * @param {Array<T | PromiseLike<T>>} values
|
||||
// * @returns {Promise<PromiseSettledResult<T>[]>}
|
||||
// */
|
||||
// primordials.SafePromiseAllSettled = (values) =>
|
||||
// // Wrapping on a new Promise is necessary to not expose the SafePromise
|
||||
// // prototype to user-land.
|
||||
// new Promise((a, b) =>
|
||||
// SafePromise.allSettled(arrayToSafePromiseIterable(values)).then(a, b)
|
||||
// );
|
||||
|
||||
// /**
|
||||
// * The any function returns a promise that is fulfilled by the first given
|
||||
// * promise to be fulfilled, or rejected with an AggregateError containing
|
||||
// * an array of rejection reasons if all of the given promises are rejected.
|
||||
// * It resolves all elements of the passed iterable to promises as it runs
|
||||
// * this algorithm.
|
||||
// * @template T
|
||||
// * @param {T} values
|
||||
// * @returns {Promise<Awaited<T[number]>>}
|
||||
// */
|
||||
// primordials.SafePromiseAny = (values) =>
|
||||
// // Wrapping on a new Promise is necessary to not expose the SafePromise
|
||||
// // prototype to user-land.
|
||||
// new Promise((a, b) =>
|
||||
// SafePromise.any(arrayToSafePromiseIterable(values)).then(a, b)
|
||||
// );
|
||||
|
||||
// /**
|
||||
// * Creates a Promise that is resolved or rejected when any of the provided
|
||||
// * Promises are resolved or rejected.
|
||||
// * @template T
|
||||
// * @param {T} values
|
||||
// * @returns {Promise<Awaited<T[number]>>}
|
||||
// */
|
||||
// primordials.SafePromiseRace = (values) =>
|
||||
// // Wrapping on a new Promise is necessary to not expose the SafePromise
|
||||
// // prototype to user-land.
|
||||
// new Promise((a, b) =>
|
||||
// SafePromise.race(arrayToSafePromiseIterable(values)).then(a, b)
|
||||
// );
|
||||
|
||||
/**
|
||||
* Attaches a callback that is invoked when the Promise is settled (fulfilled or
|
||||
* rejected). The resolved value cannot be modified from the callback.
|
||||
* Prefer using async functions when possible.
|
||||
* @param {Promise<any>} thisPromise
|
||||
* @param {() => void) | undefined | null} onFinally The callback to execute
|
||||
* when the Promise is settled (fulfilled or rejected).
|
||||
* @returns A Promise for the completion of the callback.
|
||||
*/
|
||||
primordials.SafePromisePrototypeFinally = (thisPromise, onFinally) =>
|
||||
// Wrapping on a new Promise is necessary to not expose the SafePromise
|
||||
// prototype to user-land.
|
||||
new Promise((a, b) =>
|
||||
new SafePromise((a, b) => PromisePrototypeThen(thisPromise, a, b))
|
||||
.finally(onFinally)
|
||||
.then(a, b)
|
||||
);
|
||||
|
||||
// Create getter and setter for `queueMicrotask`, it hasn't been bound yet.
|
||||
let queueMicrotask = undefined;
|
||||
ObjectDefineProperty(primordials, "queueMicrotask", {
|
||||
get() {
|
||||
return queueMicrotask;
|
||||
},
|
||||
});
|
||||
primordials.setQueueMicrotask = (value) => {
|
||||
if (queueMicrotask !== undefined) {
|
||||
throw new Error("queueMicrotask is already defined");
|
||||
}
|
||||
queueMicrotask = value;
|
||||
};
|
||||
|
||||
// Renaming from `eval` is necessary because otherwise it would perform direct
|
||||
// evaluation, allowing user-land access to local variables.
|
||||
// This is because the identifier `eval` is somewhat treated as a keyword
|
||||
primordials.indirectEval = eval;
|
||||
|
||||
ObjectSetPrototypeOf(primordials, null);
|
||||
ObjectFreeze(primordials);
|
||||
|
||||
// Provide bootstrap namespace
|
||||
globalThis.__bootstrap = { primordials };
|
||||
})();
|
878
core/01_core.js
878
core/01_core.js
|
@ -1,878 +0,0 @@
|
|||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
||||
"use strict";
|
||||
|
||||
((window) => {
|
||||
const {
|
||||
Array,
|
||||
ArrayPrototypeFill,
|
||||
ArrayPrototypeMap,
|
||||
ArrayPrototypePush,
|
||||
Error,
|
||||
ErrorCaptureStackTrace,
|
||||
MapPrototypeDelete,
|
||||
MapPrototypeGet,
|
||||
MapPrototypeHas,
|
||||
MapPrototypeSet,
|
||||
ObjectAssign,
|
||||
ObjectDefineProperty,
|
||||
ObjectFreeze,
|
||||
ObjectFromEntries,
|
||||
ObjectKeys,
|
||||
Promise,
|
||||
PromiseReject,
|
||||
PromiseResolve,
|
||||
PromisePrototypeThen,
|
||||
Proxy,
|
||||
RangeError,
|
||||
ReferenceError,
|
||||
ReflectHas,
|
||||
ReflectApply,
|
||||
SafeArrayIterator,
|
||||
SafeMap,
|
||||
SafePromisePrototypeFinally,
|
||||
StringPrototypeSlice,
|
||||
StringPrototypeSplit,
|
||||
SymbolFor,
|
||||
SyntaxError,
|
||||
TypeError,
|
||||
URIError,
|
||||
setQueueMicrotask,
|
||||
} = window.__bootstrap.primordials;
|
||||
const { ops, asyncOps } = window.Deno.core;
|
||||
|
||||
const build = {
|
||||
target: "unknown",
|
||||
arch: "unknown",
|
||||
os: "unknown",
|
||||
vendor: "unknown",
|
||||
env: undefined,
|
||||
};
|
||||
|
||||
function setBuildInfo(target) {
|
||||
const { 0: arch, 1: vendor, 2: os, 3: env } = StringPrototypeSplit(
|
||||
target,
|
||||
"-",
|
||||
4,
|
||||
);
|
||||
build.target = target;
|
||||
build.arch = arch;
|
||||
build.vendor = vendor;
|
||||
build.os = os;
|
||||
build.env = env;
|
||||
ObjectFreeze(build);
|
||||
}
|
||||
|
||||
const errorMap = {};
|
||||
// Builtin v8 / JS errors
|
||||
registerErrorClass("Error", Error);
|
||||
registerErrorClass("RangeError", RangeError);
|
||||
registerErrorClass("ReferenceError", ReferenceError);
|
||||
registerErrorClass("SyntaxError", SyntaxError);
|
||||
registerErrorClass("TypeError", TypeError);
|
||||
registerErrorClass("URIError", URIError);
|
||||
|
||||
let nextPromiseId = 1;
|
||||
const promiseMap = new SafeMap();
|
||||
const RING_SIZE = 4 * 1024;
|
||||
const NO_PROMISE = null; // Alias to null is faster than plain nulls
|
||||
const promiseRing = ArrayPrototypeFill(new Array(RING_SIZE), NO_PROMISE);
|
||||
// TODO(bartlomieju): it future use `v8::Private` so it's not visible
|
||||
// to users. Currently missing bindings.
|
||||
const promiseIdSymbol = SymbolFor("Deno.core.internalPromiseId");
|
||||
|
||||
let opCallTracingEnabled = false;
|
||||
const opCallTraces = new SafeMap();
|
||||
|
||||
function enableOpCallTracing() {
|
||||
opCallTracingEnabled = true;
|
||||
}
|
||||
|
||||
function isOpCallTracingEnabled() {
|
||||
return opCallTracingEnabled;
|
||||
}
|
||||
|
||||
function movePromise(promiseId) {
|
||||
const idx = promiseId % RING_SIZE;
|
||||
// Move old promise from ring to map
|
||||
const oldPromise = promiseRing[idx];
|
||||
if (oldPromise !== NO_PROMISE) {
|
||||
const oldPromiseId = promiseId - RING_SIZE;
|
||||
MapPrototypeSet(promiseMap, oldPromiseId, oldPromise);
|
||||
}
|
||||
return promiseRing[idx] = NO_PROMISE;
|
||||
}
|
||||
|
||||
function setPromise(promiseId) {
|
||||
const idx = promiseId % RING_SIZE;
|
||||
// Move old promise from ring to map
|
||||
const oldPromise = promiseRing[idx];
|
||||
if (oldPromise !== NO_PROMISE) {
|
||||
const oldPromiseId = promiseId - RING_SIZE;
|
||||
MapPrototypeSet(promiseMap, oldPromiseId, oldPromise);
|
||||
}
|
||||
// Set new promise
|
||||
return promiseRing[idx] = newPromise();
|
||||
}
|
||||
|
||||
function getPromise(promiseId) {
|
||||
// Check if out of ring bounds, fallback to map
|
||||
const outOfBounds = promiseId < nextPromiseId - RING_SIZE;
|
||||
if (outOfBounds) {
|
||||
const promise = MapPrototypeGet(promiseMap, promiseId);
|
||||
MapPrototypeDelete(promiseMap, promiseId);
|
||||
return promise;
|
||||
}
|
||||
// Otherwise take from ring
|
||||
const idx = promiseId % RING_SIZE;
|
||||
const promise = promiseRing[idx];
|
||||
promiseRing[idx] = NO_PROMISE;
|
||||
return promise;
|
||||
}
|
||||
|
||||
function newPromise() {
|
||||
let resolve, reject;
|
||||
const promise = new Promise((resolve_, reject_) => {
|
||||
resolve = resolve_;
|
||||
reject = reject_;
|
||||
});
|
||||
promise.resolve = resolve;
|
||||
promise.reject = reject;
|
||||
return promise;
|
||||
}
|
||||
|
||||
function hasPromise(promiseId) {
|
||||
// Check if out of ring bounds, fallback to map
|
||||
const outOfBounds = promiseId < nextPromiseId - RING_SIZE;
|
||||
if (outOfBounds) {
|
||||
return MapPrototypeHas(promiseMap, promiseId);
|
||||
}
|
||||
// Otherwise check it in ring
|
||||
const idx = promiseId % RING_SIZE;
|
||||
return promiseRing[idx] != NO_PROMISE;
|
||||
}
|
||||
|
||||
const macrotaskCallbacks = [];
|
||||
const nextTickCallbacks = [];
|
||||
|
||||
function setMacrotaskCallback(cb) {
|
||||
ArrayPrototypePush(macrotaskCallbacks, cb);
|
||||
}
|
||||
|
||||
function setNextTickCallback(cb) {
|
||||
ArrayPrototypePush(nextTickCallbacks, cb);
|
||||
}
|
||||
|
||||
// This function has variable number of arguments. The last argument describes
|
||||
// if there's a "next tick" scheduled by the Node.js compat layer. Arguments
|
||||
// before last are alternating integers and any values that describe the
|
||||
// responses of async ops.
|
||||
function eventLoopTick() {
|
||||
// First respond to all pending ops.
|
||||
for (let i = 0; i < arguments.length - 1; i += 2) {
|
||||
const promiseId = arguments[i];
|
||||
const res = arguments[i + 1];
|
||||
const promise = getPromise(promiseId);
|
||||
promise.resolve(res);
|
||||
}
|
||||
// Drain nextTick queue if there's a tick scheduled.
|
||||
if (arguments[arguments.length - 1]) {
|
||||
for (let i = 0; i < nextTickCallbacks.length; i++) {
|
||||
nextTickCallbacks[i]();
|
||||
}
|
||||
} else {
|
||||
ops.op_run_microtasks();
|
||||
}
|
||||
// Finally drain macrotask queue.
|
||||
for (let i = 0; i < macrotaskCallbacks.length; i++) {
|
||||
const cb = macrotaskCallbacks[i];
|
||||
while (true) {
|
||||
const res = cb();
|
||||
|
||||
// If callback returned `undefined` then it has no work to do, we don't
|
||||
// need to perform microtask checkpoint.
|
||||
if (res === undefined) {
|
||||
break;
|
||||
}
|
||||
|
||||
ops.op_run_microtasks();
|
||||
// If callback returned `true` then it has no more work to do, stop
|
||||
// calling it then.
|
||||
if (res === true) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function registerErrorClass(className, errorClass) {
|
||||
registerErrorBuilder(className, (msg) => new errorClass(msg));
|
||||
}
|
||||
|
||||
function registerErrorBuilder(className, errorBuilder) {
|
||||
if (typeof errorMap[className] !== "undefined") {
|
||||
throw new TypeError(`Error class for "${className}" already registered`);
|
||||
}
|
||||
errorMap[className] = errorBuilder;
|
||||
}
|
||||
|
||||
function buildCustomError(className, message, code) {
|
||||
let error;
|
||||
try {
|
||||
error = errorMap[className]?.(message);
|
||||
} catch (e) {
|
||||
throw new Error(
|
||||
`Unable to build custom error for "${className}"\n ${e.message}`,
|
||||
);
|
||||
}
|
||||
// Strip buildCustomError() calls from stack trace
|
||||
if (typeof error == "object") {
|
||||
ErrorCaptureStackTrace(error, buildCustomError);
|
||||
if (code) {
|
||||
error.code = code;
|
||||
}
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
function unwrapOpError(hideFunction) {
|
||||
return (res) => {
|
||||
// .$err_class_name is a special key that should only exist on errors
|
||||
const className = res?.$err_class_name;
|
||||
if (!className) {
|
||||
return res;
|
||||
}
|
||||
|
||||
const errorBuilder = errorMap[className];
|
||||
const err = errorBuilder ? errorBuilder(res.message) : new Error(
|
||||
`Unregistered error class: "${className}"\n ${res.message}\n Classes of errors returned from ops should be registered via Deno.core.registerErrorClass().`,
|
||||
);
|
||||
// Set .code if error was a known OS error, see error_codes.rs
|
||||
if (res.code) {
|
||||
err.code = res.code;
|
||||
}
|
||||
// Strip unwrapOpResult() and errorBuilder() calls from stack trace
|
||||
ErrorCaptureStackTrace(err, hideFunction);
|
||||
throw err;
|
||||
};
|
||||
}
|
||||
|
||||
function unwrapOpResultNewPromise(id, res, hideFunction) {
|
||||
// .$err_class_name is a special key that should only exist on errors
|
||||
if (res?.$err_class_name) {
|
||||
const className = res.$err_class_name;
|
||||
const errorBuilder = errorMap[className];
|
||||
const err = errorBuilder ? errorBuilder(res.message) : new Error(
|
||||
`Unregistered error class: "${className}"\n ${res.message}\n Classes of errors returned from ops should be registered via Deno.core.registerErrorClass().`,
|
||||
);
|
||||
// Set .code if error was a known OS error, see error_codes.rs
|
||||
if (res.code) {
|
||||
err.code = res.code;
|
||||
}
|
||||
// Strip unwrapOpResult() and errorBuilder() calls from stack trace
|
||||
ErrorCaptureStackTrace(err, hideFunction);
|
||||
return PromiseReject(err);
|
||||
}
|
||||
const promise = PromiseResolve(res);
|
||||
promise[promiseIdSymbol] = id;
|
||||
return promise;
|
||||
}
|
||||
|
||||
/*
|
||||
Basic codegen.
|
||||
|
||||
TODO(mmastrac): automate this (handlebars?)
|
||||
|
||||
let s = "";
|
||||
const vars = "abcdefghijklm";
|
||||
for (let i = 0; i < 10; i++) {
|
||||
let args = "";
|
||||
for (let j = 0; j < i; j++) {
|
||||
args += `${vars[j]},`;
|
||||
}
|
||||
s += `
|
||||
case ${i}:
|
||||
fn = function async_op_${i}(${args}) {
|
||||
const id = nextPromiseId++;
|
||||
try {
|
||||
const maybeResult = originalOp(id, ${args});
|
||||
if (maybeResult !== undefined) {
|
||||
movePromise(id);
|
||||
return unwrapOpResultNewPromise(id, maybeResult, async_op_${i});
|
||||
}
|
||||
} catch (err) {
|
||||
movePromise(id);
|
||||
ErrorCaptureStackTrace(err, async_op_${i});
|
||||
return PromiseReject(err);
|
||||
}
|
||||
let promise = PromisePrototypeThen(setPromise(id), unwrapOpError(eventLoopTick));
|
||||
promise = handleOpCallTracing(opName, id, promise);
|
||||
promise[promiseIdSymbol] = id;
|
||||
return promise;
|
||||
};
|
||||
break;
|
||||
`;
|
||||
}
|
||||
*/
|
||||
|
||||
// This function is called once per async stub
|
||||
function asyncStub(opName, args) {
|
||||
setUpAsyncStub(opName);
|
||||
return ReflectApply(ops[opName], undefined, args);
|
||||
}
|
||||
|
||||
function setUpAsyncStub(opName) {
|
||||
const originalOp = asyncOps[opName];
|
||||
let fn;
|
||||
// The body of this switch statement can be generated using the script above.
|
||||
switch (originalOp.length - 1) {
|
||||
case 0:
|
||||
fn = function async_op_0() {
|
||||
const id = nextPromiseId++;
|
||||
try {
|
||||
const maybeResult = originalOp(id);
|
||||
if (maybeResult !== undefined) {
|
||||
movePromise(id);
|
||||
return unwrapOpResultNewPromise(id, maybeResult, async_op_0);
|
||||
}
|
||||
} catch (err) {
|
||||
movePromise(id);
|
||||
ErrorCaptureStackTrace(err, async_op_0);
|
||||
return PromiseReject(err);
|
||||
}
|
||||
let promise = PromisePrototypeThen(
|
||||
setPromise(id),
|
||||
unwrapOpError(eventLoopTick),
|
||||
);
|
||||
promise = handleOpCallTracing(opName, id, promise);
|
||||
promise[promiseIdSymbol] = id;
|
||||
return promise;
|
||||
};
|
||||
break;
|
||||
|
||||
case 1:
|
||||
fn = function async_op_1(a) {
|
||||
const id = nextPromiseId++;
|
||||
try {
|
||||
const maybeResult = originalOp(id, a);
|
||||
if (maybeResult !== undefined) {
|
||||
movePromise(id);
|
||||
return unwrapOpResultNewPromise(id, maybeResult, async_op_1);
|
||||
}
|
||||
} catch (err) {
|
||||
movePromise(id);
|
||||
ErrorCaptureStackTrace(err, async_op_1);
|
||||
return PromiseReject(err);
|
||||
}
|
||||
let promise = PromisePrototypeThen(
|
||||
setPromise(id),
|
||||
unwrapOpError(eventLoopTick),
|
||||
);
|
||||
promise = handleOpCallTracing(opName, id, promise);
|
||||
promise[promiseIdSymbol] = id;
|
||||
return promise;
|
||||
};
|
||||
break;
|
||||
|
||||
case 2:
|
||||
fn = function async_op_2(a, b) {
|
||||
const id = nextPromiseId++;
|
||||
try {
|
||||
const maybeResult = originalOp(id, a, b);
|
||||
if (maybeResult !== undefined) {
|
||||
movePromise(id);
|
||||
return unwrapOpResultNewPromise(id, maybeResult, async_op_2);
|
||||
}
|
||||
} catch (err) {
|
||||
movePromise(id);
|
||||
ErrorCaptureStackTrace(err, async_op_2);
|
||||
return PromiseReject(err);
|
||||
}
|
||||
let promise = PromisePrototypeThen(
|
||||
setPromise(id),
|
||||
unwrapOpError(eventLoopTick),
|
||||
);
|
||||
promise = handleOpCallTracing(opName, id, promise);
|
||||
promise[promiseIdSymbol] = id;
|
||||
return promise;
|
||||
};
|
||||
break;
|
||||
|
||||
case 3:
|
||||
fn = function async_op_3(a, b, c) {
|
||||
const id = nextPromiseId++;
|
||||
try {
|
||||
const maybeResult = originalOp(id, a, b, c);
|
||||
if (maybeResult !== undefined) {
|
||||
movePromise(id);
|
||||
return unwrapOpResultNewPromise(id, maybeResult, async_op_3);
|
||||
}
|
||||
} catch (err) {
|
||||
movePromise(id);
|
||||
ErrorCaptureStackTrace(err, async_op_3);
|
||||
return PromiseReject(err);
|
||||
}
|
||||
let promise = PromisePrototypeThen(
|
||||
setPromise(id),
|
||||
unwrapOpError(eventLoopTick),
|
||||
);
|
||||
promise = handleOpCallTracing(opName, id, promise);
|
||||
promise[promiseIdSymbol] = id;
|
||||
return promise;
|
||||
};
|
||||
break;
|
||||
|
||||
case 4:
|
||||
fn = function async_op_4(a, b, c, d) {
|
||||
const id = nextPromiseId++;
|
||||
try {
|
||||
const maybeResult = originalOp(id, a, b, c, d);
|
||||
if (maybeResult !== undefined) {
|
||||
movePromise(id);
|
||||
return unwrapOpResultNewPromise(id, maybeResult, async_op_4);
|
||||
}
|
||||
} catch (err) {
|
||||
movePromise(id);
|
||||
ErrorCaptureStackTrace(err, async_op_4);
|
||||
return PromiseReject(err);
|
||||
}
|
||||
let promise = PromisePrototypeThen(
|
||||
setPromise(id),
|
||||
unwrapOpError(eventLoopTick),
|
||||
);
|
||||
promise = handleOpCallTracing(opName, id, promise);
|
||||
promise[promiseIdSymbol] = id;
|
||||
return promise;
|
||||
};
|
||||
break;
|
||||
|
||||
case 5:
|
||||
fn = function async_op_5(a, b, c, d, e) {
|
||||
const id = nextPromiseId++;
|
||||
try {
|
||||
const maybeResult = originalOp(id, a, b, c, d, e);
|
||||
if (maybeResult !== undefined) {
|
||||
movePromise(id);
|
||||
return unwrapOpResultNewPromise(id, maybeResult, async_op_5);
|
||||
}
|
||||
} catch (err) {
|
||||
movePromise(id);
|
||||
ErrorCaptureStackTrace(err, async_op_5);
|
||||
return PromiseReject(err);
|
||||
}
|
||||
let promise = PromisePrototypeThen(
|
||||
setPromise(id),
|
||||
unwrapOpError(eventLoopTick),
|
||||
);
|
||||
promise = handleOpCallTracing(opName, id, promise);
|
||||
promise[promiseIdSymbol] = id;
|
||||
return promise;
|
||||
};
|
||||
break;
|
||||
|
||||
case 6:
|
||||
fn = function async_op_6(a, b, c, d, e, f) {
|
||||
const id = nextPromiseId++;
|
||||
try {
|
||||
const maybeResult = originalOp(id, a, b, c, d, e, f);
|
||||
if (maybeResult !== undefined) {
|
||||
movePromise(id);
|
||||
return unwrapOpResultNewPromise(id, maybeResult, async_op_6);
|
||||
}
|
||||
} catch (err) {
|
||||
movePromise(id);
|
||||
ErrorCaptureStackTrace(err, async_op_6);
|
||||
return PromiseReject(err);
|
||||
}
|
||||
let promise = PromisePrototypeThen(
|
||||
setPromise(id),
|
||||
unwrapOpError(eventLoopTick),
|
||||
);
|
||||
promise = handleOpCallTracing(opName, id, promise);
|
||||
promise[promiseIdSymbol] = id;
|
||||
return promise;
|
||||
};
|
||||
break;
|
||||
|
||||
case 7:
|
||||
fn = function async_op_7(a, b, c, d, e, f, g) {
|
||||
const id = nextPromiseId++;
|
||||
try {
|
||||
const maybeResult = originalOp(id, a, b, c, d, e, f, g);
|
||||
if (maybeResult !== undefined) {
|
||||
movePromise(id);
|
||||
return unwrapOpResultNewPromise(id, maybeResult, async_op_7);
|
||||
}
|
||||
} catch (err) {
|
||||
movePromise(id);
|
||||
ErrorCaptureStackTrace(err, async_op_7);
|
||||
return PromiseReject(err);
|
||||
}
|
||||
let promise = PromisePrototypeThen(
|
||||
setPromise(id),
|
||||
unwrapOpError(eventLoopTick),
|
||||
);
|
||||
promise = handleOpCallTracing(opName, id, promise);
|
||||
promise[promiseIdSymbol] = id;
|
||||
return promise;
|
||||
};
|
||||
break;
|
||||
|
||||
case 8:
|
||||
fn = function async_op_8(a, b, c, d, e, f, g, h) {
|
||||
const id = nextPromiseId++;
|
||||
try {
|
||||
const maybeResult = originalOp(id, a, b, c, d, e, f, g, h);
|
||||
if (maybeResult !== undefined) {
|
||||
movePromise(id);
|
||||
return unwrapOpResultNewPromise(id, maybeResult, async_op_8);
|
||||
}
|
||||
} catch (err) {
|
||||
movePromise(id);
|
||||
ErrorCaptureStackTrace(err, async_op_8);
|
||||
return PromiseReject(err);
|
||||
}
|
||||
let promise = PromisePrototypeThen(
|
||||
setPromise(id),
|
||||
unwrapOpError(eventLoopTick),
|
||||
);
|
||||
promise = handleOpCallTracing(opName, id, promise);
|
||||
promise[promiseIdSymbol] = id;
|
||||
return promise;
|
||||
};
|
||||
break;
|
||||
|
||||
case 9:
|
||||
fn = function async_op_9(a, b, c, d, e, f, g, h, i) {
|
||||
const id = nextPromiseId++;
|
||||
try {
|
||||
const maybeResult = originalOp(id, a, b, c, d, e, f, g, h, i);
|
||||
if (maybeResult !== undefined) {
|
||||
movePromise(id);
|
||||
return unwrapOpResultNewPromise(id, maybeResult, async_op_9);
|
||||
}
|
||||
} catch (err) {
|
||||
movePromise(id);
|
||||
ErrorCaptureStackTrace(err, async_op_9);
|
||||
return PromiseReject(err);
|
||||
}
|
||||
let promise = PromisePrototypeThen(
|
||||
setPromise(id),
|
||||
unwrapOpError(eventLoopTick),
|
||||
);
|
||||
promise = handleOpCallTracing(opName, id, promise);
|
||||
promise[promiseIdSymbol] = id;
|
||||
return promise;
|
||||
};
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Error(
|
||||
`Too many arguments for async op codegen (length of ${opName} was ${
|
||||
originalOp.length - 1
|
||||
})`,
|
||||
);
|
||||
}
|
||||
ObjectDefineProperty(fn, "name", {
|
||||
value: opName,
|
||||
configurable: false,
|
||||
writable: false,
|
||||
});
|
||||
return (ops[opName] = fn);
|
||||
}
|
||||
|
||||
function opAsync(name, ...args) {
|
||||
const id = nextPromiseId++;
|
||||
try {
|
||||
const maybeResult = asyncOps[name](id, ...new SafeArrayIterator(args));
|
||||
if (maybeResult !== undefined) {
|
||||
movePromise(id);
|
||||
return unwrapOpResultNewPromise(id, maybeResult, opAsync);
|
||||
}
|
||||
} catch (err) {
|
||||
movePromise(id);
|
||||
if (!ReflectHas(asyncOps, name)) {
|
||||
return PromiseReject(new TypeError(`${name} is not a registered op`));
|
||||
}
|
||||
ErrorCaptureStackTrace(err, opAsync);
|
||||
return PromiseReject(err);
|
||||
}
|
||||
let promise = PromisePrototypeThen(
|
||||
setPromise(id),
|
||||
unwrapOpError(eventLoopTick),
|
||||
);
|
||||
promise = handleOpCallTracing(name, id, promise);
|
||||
promise[promiseIdSymbol] = id;
|
||||
return promise;
|
||||
}
|
||||
|
||||
function handleOpCallTracing(opName, promiseId, p) {
|
||||
if (opCallTracingEnabled) {
|
||||
const stack = StringPrototypeSlice(new Error().stack, 6);
|
||||
MapPrototypeSet(opCallTraces, promiseId, { opName, stack });
|
||||
return SafePromisePrototypeFinally(
|
||||
p,
|
||||
() => MapPrototypeDelete(opCallTraces, promiseId),
|
||||
);
|
||||
} else {
|
||||
return p;
|
||||
}
|
||||
}
|
||||
|
||||
function refOp(promiseId) {
|
||||
if (!hasPromise(promiseId)) {
|
||||
return;
|
||||
}
|
||||
ops.op_ref_op(promiseId);
|
||||
}
|
||||
|
||||
function unrefOp(promiseId) {
|
||||
if (!hasPromise(promiseId)) {
|
||||
return;
|
||||
}
|
||||
ops.op_unref_op(promiseId);
|
||||
}
|
||||
|
||||
function resources() {
|
||||
return ObjectFromEntries(ops.op_resources());
|
||||
}
|
||||
|
||||
function metrics() {
|
||||
const { 0: aggregate, 1: perOps } = ops.op_metrics();
|
||||
aggregate.ops = ObjectFromEntries(ArrayPrototypeMap(
|
||||
ops.op_op_names(),
|
||||
(opName, opId) => [opName, perOps[opId]],
|
||||
));
|
||||
return aggregate;
|
||||
}
|
||||
|
||||
let reportExceptionCallback = undefined;
|
||||
|
||||
// Used to report errors thrown from functions passed to `queueMicrotask()`.
|
||||
// The callback will be passed the thrown error. For example, you can use this
|
||||
// to dispatch an error event to the global scope.
|
||||
// In other words, set the implementation for
|
||||
// https://html.spec.whatwg.org/multipage/webappapis.html#report-the-exception
|
||||
function setReportExceptionCallback(cb) {
|
||||
if (typeof cb != "function") {
|
||||
throw new TypeError("expected a function");
|
||||
}
|
||||
reportExceptionCallback = cb;
|
||||
}
|
||||
|
||||
function queueMicrotask(cb) {
|
||||
if (typeof cb != "function") {
|
||||
throw new TypeError("expected a function");
|
||||
}
|
||||
return ops.op_queue_microtask(() => {
|
||||
try {
|
||||
cb();
|
||||
} catch (error) {
|
||||
if (reportExceptionCallback) {
|
||||
reportExceptionCallback(error);
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Some "extensions" rely on "BadResource" and "Interrupted" errors in the
|
||||
// JS code (eg. "deno_net") so they are provided in "Deno.core" but later
|
||||
// reexported on "Deno.errors"
|
||||
class BadResource extends Error {
|
||||
constructor(msg) {
|
||||
super(msg);
|
||||
this.name = "BadResource";
|
||||
}
|
||||
}
|
||||
const BadResourcePrototype = BadResource.prototype;
|
||||
|
||||
class Interrupted extends Error {
|
||||
constructor(msg) {
|
||||
super(msg);
|
||||
this.name = "Interrupted";
|
||||
}
|
||||
}
|
||||
const InterruptedPrototype = Interrupted.prototype;
|
||||
|
||||
const promiseHooks = [
|
||||
[], // init
|
||||
[], // before
|
||||
[], // after
|
||||
[], // resolve
|
||||
];
|
||||
|
||||
function setPromiseHooks(init, before, after, resolve) {
|
||||
const hooks = [init, before, after, resolve];
|
||||
for (let i = 0; i < hooks.length; i++) {
|
||||
const hook = hooks[i];
|
||||
// Skip if no callback was provided for this hook type.
|
||||
if (hook == null) {
|
||||
continue;
|
||||
}
|
||||
// Verify that the type of `hook` is a function.
|
||||
if (typeof hook !== "function") {
|
||||
throw new TypeError(`Expected function at position ${i}`);
|
||||
}
|
||||
// Add the hook to the list.
|
||||
ArrayPrototypePush(promiseHooks[i], hook);
|
||||
}
|
||||
|
||||
const wrappedHooks = ArrayPrototypeMap(promiseHooks, (hooks) => {
|
||||
switch (hooks.length) {
|
||||
case 0:
|
||||
return undefined;
|
||||
case 1:
|
||||
return hooks[0];
|
||||
case 2:
|
||||
return create2xHookWrapper(hooks[0], hooks[1]);
|
||||
case 3:
|
||||
return create3xHookWrapper(hooks[0], hooks[1], hooks[2]);
|
||||
default:
|
||||
return createHookListWrapper(hooks);
|
||||
}
|
||||
|
||||
// The following functions are used to create wrapper functions that call
|
||||
// all the hooks in a list of a certain length. The reason to use a
|
||||
// function that creates a wrapper is to minimize the number of objects
|
||||
// captured in the closure.
|
||||
function create2xHookWrapper(hook1, hook2) {
|
||||
return function (promise, parent) {
|
||||
hook1(promise, parent);
|
||||
hook2(promise, parent);
|
||||
};
|
||||
}
|
||||
function create3xHookWrapper(hook1, hook2, hook3) {
|
||||
return function (promise, parent) {
|
||||
hook1(promise, parent);
|
||||
hook2(promise, parent);
|
||||
hook3(promise, parent);
|
||||
};
|
||||
}
|
||||
function createHookListWrapper(hooks) {
|
||||
return function (promise, parent) {
|
||||
for (let i = 0; i < hooks.length; i++) {
|
||||
const hook = hooks[i];
|
||||
hook(promise, parent);
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
ops.op_set_promise_hooks(
|
||||
wrappedHooks[0],
|
||||
wrappedHooks[1],
|
||||
wrappedHooks[2],
|
||||
wrappedHooks[3],
|
||||
);
|
||||
}
|
||||
|
||||
// Eagerly initialize ops for snapshot purposes
|
||||
for (const opName of new SafeArrayIterator(ObjectKeys(asyncOps))) {
|
||||
setUpAsyncStub(opName);
|
||||
}
|
||||
|
||||
function ensureFastOps() {
|
||||
return new Proxy({}, {
|
||||
get(_target, opName) {
|
||||
if (ops[opName] === undefined) {
|
||||
throw new Error(`Unknown or disabled op '${opName}'`);
|
||||
}
|
||||
if (asyncOps[opName] !== undefined) {
|
||||
return setUpAsyncStub(opName);
|
||||
} else {
|
||||
return ops[opName];
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const {
|
||||
op_close: close,
|
||||
op_try_close: tryClose,
|
||||
op_read: read,
|
||||
op_read_all: readAll,
|
||||
op_write: write,
|
||||
op_write_all: writeAll,
|
||||
op_read_sync: readSync,
|
||||
op_write_sync: writeSync,
|
||||
op_shutdown: shutdown,
|
||||
} = ensureFastOps();
|
||||
|
||||
// Extra Deno.core.* exports
|
||||
const core = ObjectAssign(globalThis.Deno.core, {
|
||||
asyncStub,
|
||||
ensureFastOps,
|
||||
opAsync,
|
||||
resources,
|
||||
metrics,
|
||||
registerErrorBuilder,
|
||||
registerErrorClass,
|
||||
buildCustomError,
|
||||
eventLoopTick,
|
||||
BadResource,
|
||||
BadResourcePrototype,
|
||||
Interrupted,
|
||||
InterruptedPrototype,
|
||||
enableOpCallTracing,
|
||||
isOpCallTracingEnabled,
|
||||
opCallTraces,
|
||||
refOp,
|
||||
unrefOp,
|
||||
setReportExceptionCallback,
|
||||
setPromiseHooks,
|
||||
close,
|
||||
tryClose,
|
||||
read,
|
||||
readAll,
|
||||
write,
|
||||
writeAll,
|
||||
readSync,
|
||||
writeSync,
|
||||
shutdown,
|
||||
print: (msg, isErr) => ops.op_print(msg, isErr),
|
||||
setMacrotaskCallback,
|
||||
setNextTickCallback,
|
||||
runMicrotasks: () => ops.op_run_microtasks(),
|
||||
hasTickScheduled: () => ops.op_has_tick_scheduled(),
|
||||
setHasTickScheduled: (bool) => ops.op_set_has_tick_scheduled(bool),
|
||||
evalContext: (
|
||||
source,
|
||||
specifier,
|
||||
) => ops.op_eval_context(source, specifier),
|
||||
createHostObject: () => ops.op_create_host_object(),
|
||||
encode: (text) => ops.op_encode(text),
|
||||
decode: (buffer) => ops.op_decode(buffer),
|
||||
serialize: (
|
||||
value,
|
||||
options,
|
||||
errorCallback,
|
||||
) => ops.op_serialize(value, options, errorCallback),
|
||||
deserialize: (buffer, options) => ops.op_deserialize(buffer, options),
|
||||
getPromiseDetails: (promise) => ops.op_get_promise_details(promise),
|
||||
getProxyDetails: (proxy) => ops.op_get_proxy_details(proxy),
|
||||
isProxy: (value) => ops.op_is_proxy(value),
|
||||
memoryUsage: () => ops.op_memory_usage(),
|
||||
setWasmStreamingCallback: (fn) => ops.op_set_wasm_streaming_callback(fn),
|
||||
abortWasmStreaming: (
|
||||
rid,
|
||||
error,
|
||||
) => ops.op_abort_wasm_streaming(rid, error),
|
||||
destructureError: (error) => ops.op_destructure_error(error),
|
||||
opNames: () => ops.op_op_names(),
|
||||
eventLoopHasMoreWork: () => ops.op_event_loop_has_more_work(),
|
||||
setPromiseRejectCallback: (fn) => ops.op_set_promise_reject_callback(fn),
|
||||
byteLength: (str) => ops.op_str_byte_length(str),
|
||||
build,
|
||||
setBuildInfo,
|
||||
});
|
||||
|
||||
ObjectAssign(globalThis.__bootstrap, { core });
|
||||
const internals = {};
|
||||
ObjectAssign(globalThis.__bootstrap, { internals });
|
||||
ObjectAssign(globalThis.Deno, { core });
|
||||
|
||||
// Direct bindings on `globalThis`
|
||||
ObjectAssign(globalThis, { queueMicrotask });
|
||||
setQueueMicrotask(queueMicrotask);
|
||||
})(globalThis);
|
157
core/02_error.js
157
core/02_error.js
|
@ -1,157 +0,0 @@
|
|||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
||||
"use strict";
|
||||
|
||||
((window) => {
|
||||
const core = Deno.core;
|
||||
const ops = core.ops;
|
||||
const {
|
||||
Error,
|
||||
ObjectFreeze,
|
||||
ObjectAssign,
|
||||
StringPrototypeStartsWith,
|
||||
StringPrototypeEndsWith,
|
||||
ObjectDefineProperties,
|
||||
ArrayPrototypePush,
|
||||
ArrayPrototypeMap,
|
||||
ArrayPrototypeJoin,
|
||||
} = window.__bootstrap.primordials;
|
||||
|
||||
// Keep in sync with `cli/fmt_errors.rs`.
|
||||
function formatLocation(cse) {
|
||||
if (cse.isNative) {
|
||||
return "native";
|
||||
}
|
||||
let result = "";
|
||||
if (cse.fileName) {
|
||||
result += ops.op_format_file_name(cse.fileName);
|
||||
} else {
|
||||
if (cse.isEval) {
|
||||
if (cse.evalOrigin == null) {
|
||||
throw new Error("assert evalOrigin");
|
||||
}
|
||||
result += `${cse.evalOrigin}, `;
|
||||
}
|
||||
result += "<anonymous>";
|
||||
}
|
||||
if (cse.lineNumber != null) {
|
||||
result += `:${cse.lineNumber}`;
|
||||
if (cse.columnNumber != null) {
|
||||
result += `:${cse.columnNumber}`;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Keep in sync with `cli/fmt_errors.rs`.
|
||||
function formatCallSiteEval(cse) {
|
||||
let result = "";
|
||||
if (cse.isAsync) {
|
||||
result += "async ";
|
||||
}
|
||||
if (cse.isPromiseAll) {
|
||||
result += `Promise.all (index ${cse.promiseIndex})`;
|
||||
return result;
|
||||
}
|
||||
const isMethodCall = !(cse.isToplevel || cse.isConstructor);
|
||||
if (isMethodCall) {
|
||||
if (cse.functionName) {
|
||||
if (cse.typeName) {
|
||||
if (!StringPrototypeStartsWith(cse.functionName, cse.typeName)) {
|
||||
result += `${cse.typeName}.`;
|
||||
}
|
||||
}
|
||||
result += cse.functionName;
|
||||
if (cse.methodName) {
|
||||
if (!StringPrototypeEndsWith(cse.functionName, cse.methodName)) {
|
||||
result += ` [as ${cse.methodName}]`;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (cse.typeName) {
|
||||
result += `${cse.typeName}.`;
|
||||
}
|
||||
if (cse.methodName) {
|
||||
result += cse.methodName;
|
||||
} else {
|
||||
result += "<anonymous>";
|
||||
}
|
||||
}
|
||||
} else if (cse.isConstructor) {
|
||||
result += "new ";
|
||||
if (cse.functionName) {
|
||||
result += cse.functionName;
|
||||
} else {
|
||||
result += "<anonymous>";
|
||||
}
|
||||
} else if (cse.functionName) {
|
||||
result += cse.functionName;
|
||||
} else {
|
||||
result += formatLocation(cse);
|
||||
return result;
|
||||
}
|
||||
|
||||
result += ` (${formatLocation(cse)})`;
|
||||
return result;
|
||||
}
|
||||
|
||||
function evaluateCallSite(callSite) {
|
||||
return {
|
||||
this: callSite.getThis(),
|
||||
typeName: callSite.getTypeName(),
|
||||
function: callSite.getFunction(),
|
||||
functionName: callSite.getFunctionName(),
|
||||
methodName: callSite.getMethodName(),
|
||||
fileName: callSite.getFileName(),
|
||||
lineNumber: callSite.getLineNumber(),
|
||||
columnNumber: callSite.getColumnNumber(),
|
||||
evalOrigin: callSite.getEvalOrigin(),
|
||||
isToplevel: callSite.isToplevel(),
|
||||
isEval: callSite.isEval(),
|
||||
isNative: callSite.isNative(),
|
||||
isConstructor: callSite.isConstructor(),
|
||||
isAsync: callSite.isAsync(),
|
||||
isPromiseAll: callSite.isPromiseAll(),
|
||||
promiseIndex: callSite.getPromiseIndex(),
|
||||
};
|
||||
}
|
||||
|
||||
function sourceMapCallSiteEval(cse) {
|
||||
if (cse.fileName && cse.lineNumber != null && cse.columnNumber != null) {
|
||||
return { ...cse, ...ops.op_apply_source_map(cse) };
|
||||
}
|
||||
return cse;
|
||||
}
|
||||
|
||||
/** A function that can be used as `Error.prepareStackTrace`. */
|
||||
function prepareStackTrace(error, callSites) {
|
||||
let callSiteEvals = ArrayPrototypeMap(callSites, evaluateCallSite);
|
||||
callSiteEvals = ArrayPrototypeMap(callSiteEvals, sourceMapCallSiteEval);
|
||||
ObjectDefineProperties(error, {
|
||||
__callSiteEvals: { __proto__: null, value: [], configurable: true },
|
||||
});
|
||||
const formattedCallSites = [];
|
||||
for (let i = 0; i < callSiteEvals.length; ++i) {
|
||||
const cse = callSiteEvals[i];
|
||||
ArrayPrototypePush(error.__callSiteEvals, cse);
|
||||
ArrayPrototypePush(formattedCallSites, formatCallSiteEval(cse));
|
||||
}
|
||||
const message = error.message !== undefined ? error.message : "";
|
||||
const name = error.name !== undefined ? error.name : "Error";
|
||||
let messageLine;
|
||||
if (name != "" && message != "") {
|
||||
messageLine = `${name}: ${message}`;
|
||||
} else if ((name || message) != "") {
|
||||
messageLine = name || message;
|
||||
} else {
|
||||
messageLine = "";
|
||||
}
|
||||
return messageLine +
|
||||
ArrayPrototypeJoin(
|
||||
ArrayPrototypeMap(formattedCallSites, (s) => `\n at ${s}`),
|
||||
"",
|
||||
);
|
||||
}
|
||||
|
||||
ObjectAssign(globalThis.__bootstrap.core, { prepareStackTrace });
|
||||
ObjectFreeze(globalThis.__bootstrap.core);
|
||||
})(this);
|
|
@ -1,50 +0,0 @@
|
|||
# Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
||||
|
||||
[package]
|
||||
name = "deno_core"
|
||||
version = "0.191.0"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
readme = "README.md"
|
||||
repository.workspace = true
|
||||
description = "A modern JavaScript/TypeScript runtime built with V8, Rust, and Tokio"
|
||||
|
||||
[lib]
|
||||
path = "lib.rs"
|
||||
|
||||
[features]
|
||||
default = ["v8_use_custom_libcxx"]
|
||||
v8_use_custom_libcxx = ["v8/use_custom_libcxx"]
|
||||
include_js_files_for_snapshotting = []
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
bytes.workspace = true
|
||||
deno_ops.workspace = true
|
||||
futures.workspace = true
|
||||
# Stay on 1.6 to avoid a dependency cycle in ahash https://github.com/tkaitchuck/aHash/issues/95
|
||||
# Projects not depending on ahash are unaffected as cargo will pull any 1.X that is >= 1.6.
|
||||
indexmap = "1.6"
|
||||
libc.workspace = true
|
||||
log.workspace = true
|
||||
once_cell.workspace = true
|
||||
parking_lot.workspace = true
|
||||
pin-project.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json = { workspace = true, features = ["preserve_order"] }
|
||||
serde_v8.workspace = true
|
||||
smallvec.workspace = true
|
||||
sourcemap = "6.1"
|
||||
tokio.workspace = true
|
||||
url.workspace = true
|
||||
v8.workspace = true
|
||||
|
||||
[[example]]
|
||||
name = "http_bench_json_ops"
|
||||
path = "examples/http_bench_json_ops/main.rs"
|
||||
|
||||
# These dependencies are only used for the 'http_bench_*_ops' examples.
|
||||
[dev-dependencies]
|
||||
cooked-waker = "5"
|
||||
deno_ast.workspace = true
|
|
@ -1,31 +0,0 @@
|
|||
# Deno Core Crate
|
||||
|
||||
[![crates](https://img.shields.io/crates/v/deno_core.svg)](https://crates.io/crates/deno_core)
|
||||
[![docs](https://docs.rs/deno_core/badge.svg)](https://docs.rs/deno_core)
|
||||
|
||||
The main dependency of this crate is
|
||||
[rusty_v8](https://github.com/denoland/rusty_v8), which provides the V8-Rust
|
||||
bindings.
|
||||
|
||||
This Rust crate contains the essential V8 bindings for Deno's command-line
|
||||
interface (Deno CLI). The main abstraction here is the JsRuntime which provides
|
||||
a way to execute JavaScript.
|
||||
|
||||
The JsRuntime implements an event loop abstraction for the executed code that
|
||||
keeps track of all pending tasks (async ops, dynamic module loads). It is user's
|
||||
responsibility to drive that loop by using `JsRuntime::run_event_loop` method -
|
||||
it must be executed in the context of Rust's future executor (eg. tokio, smol).
|
||||
|
||||
Rust functions can be registered in JavaScript using `deno_core::Extension`. Use
|
||||
the `Deno.core.ops.op_name()` and `Deno.core.opAsync("op_name", ...)` functions
|
||||
to trigger the op function callback. A conventional way to write ops is using
|
||||
the [`deno_ops`](https://github.com/denoland/deno/blob/main/ops) crate.
|
||||
|
||||
Documentation for this crate is thin at the moment. Please see
|
||||
[hello_world.rs](https://github.com/denoland/deno/blob/main/core/examples/hello_world.rs)
|
||||
and
|
||||
[http_bench_json_ops/main.rs](https://github.com/denoland/deno/blob/main/core/examples/http_bench_json_ops/main.rs)
|
||||
as examples of usage.
|
||||
|
||||
TypeScript support and lots of other functionality are not available at this
|
||||
layer. See the [CLI](https://github.com/denoland/deno/tree/main/cli) for that.
|
|
@ -1,793 +0,0 @@
|
|||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
||||
|
||||
use std::any::type_name;
|
||||
use std::borrow::Cow;
|
||||
use std::error::Error;
|
||||
use std::fmt;
|
||||
use std::fmt::Display;
|
||||
use std::fmt::Formatter;
|
||||
use std::io;
|
||||
use std::pin::Pin;
|
||||
use std::rc::Rc;
|
||||
|
||||
use futures::future::FusedFuture;
|
||||
use futures::future::Future;
|
||||
use futures::future::TryFuture;
|
||||
use futures::task::Context;
|
||||
use futures::task::Poll;
|
||||
use pin_project::pin_project;
|
||||
|
||||
use crate::RcLike;
|
||||
use crate::Resource;
|
||||
|
||||
use self::internal as i;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct CancelHandle {
|
||||
node: i::Node,
|
||||
}
|
||||
|
||||
impl CancelHandle {
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
pub fn new_rc() -> Rc<Self> {
|
||||
Rc::new(Self::new())
|
||||
}
|
||||
|
||||
/// Cancel all cancelable futures that are bound to this handle. Note that
|
||||
/// this method does not require a mutable reference to the `CancelHandle`.
|
||||
pub fn cancel(&self) {
|
||||
self.node.cancel();
|
||||
}
|
||||
|
||||
pub fn is_canceled(&self) -> bool {
|
||||
self.node.is_canceled()
|
||||
}
|
||||
}
|
||||
|
||||
#[pin_project(project = CancelableProjection)]
|
||||
#[derive(Debug)]
|
||||
pub enum Cancelable<F> {
|
||||
Pending {
|
||||
#[pin]
|
||||
future: F,
|
||||
#[pin]
|
||||
registration: i::Registration,
|
||||
},
|
||||
Terminated,
|
||||
}
|
||||
|
||||
impl<F: Future> Future for Cancelable<F> {
|
||||
type Output = Result<F::Output, Canceled>;
|
||||
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
|
||||
let poll_result = match self.as_mut().project() {
|
||||
CancelableProjection::Pending {
|
||||
future,
|
||||
registration,
|
||||
} => Self::poll_pending(future, registration, cx),
|
||||
CancelableProjection::Terminated => {
|
||||
panic!("{}::poll() called after completion", type_name::<Self>())
|
||||
}
|
||||
};
|
||||
// Fuse: if this Future is completed or canceled, make sure the inner
|
||||
// `future` and `registration` fields are dropped in order to unlink it from
|
||||
// its cancel handle.
|
||||
if matches!(poll_result, Poll::Ready(_)) {
|
||||
self.set(Cancelable::Terminated)
|
||||
}
|
||||
poll_result
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: Future> FusedFuture for Cancelable<F> {
|
||||
fn is_terminated(&self) -> bool {
|
||||
matches!(self, Self::Terminated)
|
||||
}
|
||||
}
|
||||
|
||||
impl Resource for CancelHandle {
|
||||
fn name(&self) -> Cow<str> {
|
||||
"cancellation".into()
|
||||
}
|
||||
|
||||
fn close(self: Rc<Self>) {
|
||||
self.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
#[pin_project(project = TryCancelableProjection)]
|
||||
#[derive(Debug)]
|
||||
pub struct TryCancelable<F> {
|
||||
#[pin]
|
||||
inner: Cancelable<F>,
|
||||
}
|
||||
|
||||
impl<F, T, E> Future for TryCancelable<F>
|
||||
where
|
||||
F: Future<Output = Result<T, E>>,
|
||||
Canceled: Into<E>,
|
||||
{
|
||||
type Output = F::Output;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
|
||||
let TryCancelableProjection { inner } = self.project();
|
||||
match inner.poll(cx) {
|
||||
Poll::Pending => Poll::Pending,
|
||||
Poll::Ready(Ok(result)) => Poll::Ready(result),
|
||||
Poll::Ready(Err(err)) => Poll::Ready(Err(err.into())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<F, T, E> FusedFuture for TryCancelable<F>
|
||||
where
|
||||
F: Future<Output = Result<T, E>>,
|
||||
Canceled: Into<E>,
|
||||
{
|
||||
fn is_terminated(&self) -> bool {
|
||||
self.inner.is_terminated()
|
||||
}
|
||||
}
|
||||
|
||||
pub trait CancelFuture
|
||||
where
|
||||
Self: Future + Sized,
|
||||
{
|
||||
fn or_cancel<H: RcLike<CancelHandle>>(
|
||||
self,
|
||||
cancel_handle: H,
|
||||
) -> Cancelable<Self> {
|
||||
Cancelable::new(self, cancel_handle.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl<F> CancelFuture for F where F: Future {}
|
||||
|
||||
pub trait CancelTryFuture
|
||||
where
|
||||
Self: TryFuture + Sized,
|
||||
Canceled: Into<Self::Error>,
|
||||
{
|
||||
fn try_or_cancel<H: RcLike<CancelHandle>>(
|
||||
self,
|
||||
cancel_handle: H,
|
||||
) -> TryCancelable<Self> {
|
||||
TryCancelable::new(self, cancel_handle.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl<F> CancelTryFuture for F
|
||||
where
|
||||
F: TryFuture,
|
||||
Canceled: Into<F::Error>,
|
||||
{
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Default, Debug, Eq, Hash, PartialEq)]
|
||||
pub struct Canceled;
|
||||
|
||||
impl Display for Canceled {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "operation canceled")
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for Canceled {}
|
||||
|
||||
impl From<Canceled> for io::Error {
|
||||
fn from(_: Canceled) -> Self {
|
||||
io::Error::new(io::ErrorKind::Interrupted, Canceled)
|
||||
}
|
||||
}
|
||||
|
||||
mod internal {
|
||||
use super::CancelHandle;
|
||||
use super::Cancelable;
|
||||
use super::Canceled;
|
||||
use super::TryCancelable;
|
||||
use crate::RcRef;
|
||||
use futures::future::Future;
|
||||
use futures::task::Context;
|
||||
use futures::task::Poll;
|
||||
use futures::task::Waker;
|
||||
use pin_project::pin_project;
|
||||
use std::any::Any;
|
||||
use std::cell::UnsafeCell;
|
||||
use std::marker::PhantomPinned;
|
||||
use std::mem::replace;
|
||||
use std::pin::Pin;
|
||||
use std::ptr::NonNull;
|
||||
use std::rc::Rc;
|
||||
use std::rc::Weak;
|
||||
|
||||
impl<F: Future> Cancelable<F> {
|
||||
pub(super) fn new(future: F, cancel_handle: RcRef<CancelHandle>) -> Self {
|
||||
let head_node = RcRef::map(cancel_handle, |r| &r.node);
|
||||
let registration = Registration::WillRegister { head_node };
|
||||
Self::Pending {
|
||||
future,
|
||||
registration,
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn poll_pending(
|
||||
future: Pin<&mut F>,
|
||||
mut registration: Pin<&mut Registration>,
|
||||
cx: &mut Context,
|
||||
) -> Poll<Result<F::Output, Canceled>> {
|
||||
// Do a cancellation check _before_ polling the inner future. If it has
|
||||
// already been canceled the inner future will not be polled.
|
||||
let node = match &*registration {
|
||||
Registration::WillRegister { head_node } => head_node,
|
||||
Registration::Registered { node } => node,
|
||||
};
|
||||
if node.is_canceled() {
|
||||
return Poll::Ready(Err(Canceled));
|
||||
}
|
||||
|
||||
match future.poll(cx) {
|
||||
Poll::Ready(res) => return Poll::Ready(Ok(res)),
|
||||
Poll::Pending => {}
|
||||
}
|
||||
|
||||
// Register this future with its `CancelHandle`, saving the `Waker` that
|
||||
// can be used to make the runtime poll this future when it is canceled.
|
||||
// When already registered, update the stored `Waker` if necessary.
|
||||
let head_node = match &*registration {
|
||||
Registration::WillRegister { .. } => {
|
||||
match registration.as_mut().project_replace(Default::default()) {
|
||||
RegistrationProjectionOwned::WillRegister { head_node } => {
|
||||
Some(head_node)
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
let node = match registration.project() {
|
||||
RegistrationProjection::Registered { node } => node,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
node.register(cx.waker(), head_node)?;
|
||||
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: Future> TryCancelable<F> {
|
||||
pub(super) fn new(future: F, cancel_handle: RcRef<CancelHandle>) -> Self {
|
||||
Self {
|
||||
inner: Cancelable::new(future, cancel_handle),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[pin_project(project = RegistrationProjection,
|
||||
project_replace = RegistrationProjectionOwned)]
|
||||
#[derive(Debug)]
|
||||
pub enum Registration {
|
||||
WillRegister {
|
||||
head_node: RcRef<Node>,
|
||||
},
|
||||
Registered {
|
||||
#[pin]
|
||||
node: Node,
|
||||
},
|
||||
}
|
||||
|
||||
impl Default for Registration {
|
||||
fn default() -> Self {
|
||||
Self::Registered {
|
||||
node: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Node {
|
||||
inner: UnsafeCell<NodeInner>,
|
||||
_pin: PhantomPinned,
|
||||
}
|
||||
|
||||
impl Node {
|
||||
/// If necessary, register a `Cancelable` node with a `CancelHandle`, and
|
||||
/// save or update the `Waker` that can wake with this cancelable future.
|
||||
pub fn register(
|
||||
&self,
|
||||
waker: &Waker,
|
||||
head_rc: Option<RcRef<Node>>,
|
||||
) -> Result<(), Canceled> {
|
||||
match head_rc.as_ref().map(RcRef::split) {
|
||||
Some((head, rc)) => {
|
||||
// Register this `Cancelable` node with a `CancelHandle` head node.
|
||||
assert_ne!(self, head);
|
||||
// TODO(piscisaureus): safety comment
|
||||
#[allow(clippy::undocumented_unsafe_blocks)]
|
||||
let self_inner = unsafe { &mut *self.inner.get() };
|
||||
// TODO(piscisaureus): safety comment
|
||||
#[allow(clippy::undocumented_unsafe_blocks)]
|
||||
let head_inner = unsafe { &mut *head.inner.get() };
|
||||
self_inner.link(waker, head_inner, rc)
|
||||
}
|
||||
None => {
|
||||
// This `Cancelable` has already been linked to a `CancelHandle` head
|
||||
// node; just update our stored `Waker` if necessary.
|
||||
// TODO(piscisaureus): safety comment
|
||||
#[allow(clippy::undocumented_unsafe_blocks)]
|
||||
let inner = unsafe { &mut *self.inner.get() };
|
||||
inner.update_waker(waker)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn cancel(&self) {
|
||||
// TODO(piscisaureus): safety comment
|
||||
#[allow(clippy::undocumented_unsafe_blocks)]
|
||||
let inner = unsafe { &mut *self.inner.get() };
|
||||
inner.cancel();
|
||||
}
|
||||
|
||||
pub fn is_canceled(&self) -> bool {
|
||||
// TODO(piscisaureus): safety comment
|
||||
#[allow(clippy::undocumented_unsafe_blocks)]
|
||||
let inner = unsafe { &mut *self.inner.get() };
|
||||
inner.is_canceled()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Node {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
inner: UnsafeCell::new(NodeInner::Unlinked),
|
||||
_pin: PhantomPinned,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Node {
|
||||
fn drop(&mut self) {
|
||||
// TODO(piscisaureus): safety comment
|
||||
#[allow(clippy::undocumented_unsafe_blocks)]
|
||||
let inner = unsafe { &mut *self.inner.get() };
|
||||
inner.unlink();
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Node {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
std::ptr::eq(self, other)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum NodeInner {
|
||||
Unlinked,
|
||||
Linked {
|
||||
kind: NodeKind,
|
||||
prev: NonNull<NodeInner>,
|
||||
next: NonNull<NodeInner>,
|
||||
},
|
||||
Canceled,
|
||||
}
|
||||
|
||||
impl NodeInner {
|
||||
fn as_non_null(&mut self) -> NonNull<Self> {
|
||||
NonNull::from(self)
|
||||
}
|
||||
|
||||
fn link(
|
||||
&mut self,
|
||||
waker: &Waker,
|
||||
head: &mut Self,
|
||||
rc_pin: &Rc<dyn Any>,
|
||||
) -> Result<(), Canceled> {
|
||||
// The future should not have been linked to a cancel handle before.
|
||||
assert!(matches!(self, NodeInner::Unlinked));
|
||||
|
||||
match head {
|
||||
NodeInner::Unlinked => {
|
||||
*head = NodeInner::Linked {
|
||||
kind: NodeKind::head(rc_pin),
|
||||
prev: self.as_non_null(),
|
||||
next: self.as_non_null(),
|
||||
};
|
||||
*self = NodeInner::Linked {
|
||||
kind: NodeKind::item(waker),
|
||||
prev: head.as_non_null(),
|
||||
next: head.as_non_null(),
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
NodeInner::Linked {
|
||||
kind: NodeKind::Head { .. },
|
||||
prev: next_prev_nn,
|
||||
..
|
||||
} => {
|
||||
// TODO(piscisaureus): safety comment
|
||||
#[allow(clippy::undocumented_unsafe_blocks)]
|
||||
let prev = unsafe { &mut *next_prev_nn.as_ptr() };
|
||||
match prev {
|
||||
NodeInner::Linked {
|
||||
kind: NodeKind::Item { .. },
|
||||
next: prev_next_nn,
|
||||
..
|
||||
} => {
|
||||
*self = NodeInner::Linked {
|
||||
kind: NodeKind::item(waker),
|
||||
prev: replace(next_prev_nn, self.as_non_null()),
|
||||
next: replace(prev_next_nn, self.as_non_null()),
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
NodeInner::Canceled => Err(Canceled),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn update_waker(&mut self, new_waker: &Waker) -> Result<(), Canceled> {
|
||||
match self {
|
||||
NodeInner::Unlinked => Ok(()),
|
||||
NodeInner::Linked {
|
||||
kind: NodeKind::Item { waker },
|
||||
..
|
||||
} => {
|
||||
if !waker.will_wake(new_waker) {
|
||||
*waker = new_waker.clone();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
NodeInner::Canceled => Err(Canceled),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
/// If this node is linked to other nodes, remove it from the chain. This
|
||||
/// method is called (only) by the drop handler for `Node`. It is suitable
|
||||
/// for both 'head' and 'item' nodes.
|
||||
fn unlink(&mut self) {
|
||||
if let NodeInner::Linked {
|
||||
prev: mut prev_nn,
|
||||
next: mut next_nn,
|
||||
..
|
||||
} = replace(self, NodeInner::Unlinked)
|
||||
{
|
||||
if prev_nn == next_nn {
|
||||
// There were only two nodes in this chain; after unlinking ourselves
|
||||
// the other node is no longer linked.
|
||||
// TODO(piscisaureus): safety comment
|
||||
#[allow(clippy::undocumented_unsafe_blocks)]
|
||||
let other = unsafe { prev_nn.as_mut() };
|
||||
*other = NodeInner::Unlinked;
|
||||
} else {
|
||||
// The chain had more than two nodes.
|
||||
// TODO(piscisaureus): safety comment
|
||||
#[allow(clippy::undocumented_unsafe_blocks)]
|
||||
match unsafe { prev_nn.as_mut() } {
|
||||
NodeInner::Linked {
|
||||
next: prev_next_nn, ..
|
||||
} => {
|
||||
*prev_next_nn = next_nn;
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
// TODO(piscisaureus): safety comment
|
||||
#[allow(clippy::undocumented_unsafe_blocks)]
|
||||
match unsafe { next_nn.as_mut() } {
|
||||
NodeInner::Linked {
|
||||
prev: next_prev_nn, ..
|
||||
} => {
|
||||
*next_prev_nn = prev_nn;
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Mark this node and all linked nodes for cancellation. Note that `self`
|
||||
/// must refer to a head (`CancelHandle`) node.
|
||||
fn cancel(&mut self) {
|
||||
let mut head_nn = NonNull::from(self);
|
||||
|
||||
// TODO(piscisaureus): safety comment
|
||||
#[allow(clippy::undocumented_unsafe_blocks)]
|
||||
// Mark the head node as canceled.
|
||||
let mut item_nn =
|
||||
match replace(unsafe { head_nn.as_mut() }, NodeInner::Canceled) {
|
||||
NodeInner::Linked {
|
||||
kind: NodeKind::Head { .. },
|
||||
next: next_nn,
|
||||
..
|
||||
} => next_nn,
|
||||
NodeInner::Unlinked | NodeInner::Canceled => return,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
// Cancel all item nodes in the chain, waking each stored `Waker`.
|
||||
while item_nn != head_nn {
|
||||
// TODO(piscisaureus): safety comment
|
||||
#[allow(clippy::undocumented_unsafe_blocks)]
|
||||
match replace(unsafe { item_nn.as_mut() }, NodeInner::Canceled) {
|
||||
NodeInner::Linked {
|
||||
kind: NodeKind::Item { waker },
|
||||
next: next_nn,
|
||||
..
|
||||
} => {
|
||||
waker.wake();
|
||||
item_nn = next_nn;
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true if this node has been marked for cancellation. This method
|
||||
/// may be used with both head (`CancelHandle`) and item (`Cancelable`)
|
||||
/// nodes.
|
||||
fn is_canceled(&self) -> bool {
|
||||
match self {
|
||||
NodeInner::Unlinked | NodeInner::Linked { .. } => false,
|
||||
NodeInner::Canceled => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum NodeKind {
|
||||
/// In a chain of linked nodes, the "head" node is owned by the
|
||||
/// `CancelHandle`. A chain usually contains at most one head node; however
|
||||
/// when a `CancelHandle` is dropped before the futures associated with it
|
||||
/// are dropped, a chain may temporarily contain no head node at all.
|
||||
Head {
|
||||
/// The `weak_pin` field adds adds a weak reference to the `Rc` guarding
|
||||
/// the heap allocation that contains the `CancelHandle`. Without this
|
||||
/// extra weak reference, `Rc::get_mut()` might succeed and allow the
|
||||
/// `CancelHandle` to be moved when it isn't safe to do so.
|
||||
_weak_pin: Weak<dyn Any>,
|
||||
},
|
||||
/// All item nodes in a chain are associated with a `Cancelable` head node.
|
||||
Item {
|
||||
/// If this future indeed does get canceled, the waker is needed to make
|
||||
/// sure that the canceled future gets polled as soon as possible.
|
||||
waker: Waker,
|
||||
},
|
||||
}
|
||||
|
||||
impl NodeKind {
|
||||
fn head(rc_pin: &Rc<dyn Any>) -> Self {
|
||||
let _weak_pin = Rc::downgrade(rc_pin);
|
||||
Self::Head { _weak_pin }
|
||||
}
|
||||
|
||||
fn item(waker: &Waker) -> Self {
|
||||
let waker = waker.clone();
|
||||
Self::Item { waker }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use anyhow::Error;
|
||||
use futures::future::pending;
|
||||
use futures::future::poll_fn;
|
||||
use futures::future::ready;
|
||||
use futures::future::FutureExt;
|
||||
use futures::future::TryFutureExt;
|
||||
use futures::pending;
|
||||
use futures::select;
|
||||
use futures::task::noop_waker_ref;
|
||||
use futures::task::Context;
|
||||
use futures::task::Poll;
|
||||
use std::convert::Infallible as Never;
|
||||
use std::io;
|
||||
use tokio::net::TcpStream;
|
||||
use tokio::spawn;
|
||||
use tokio::task::yield_now;
|
||||
|
||||
fn box_fused<'a, F: FusedFuture + 'a>(
|
||||
future: F,
|
||||
) -> Pin<Box<dyn FusedFuture<Output = F::Output> + 'a>> {
|
||||
Box::pin(future)
|
||||
}
|
||||
|
||||
async fn ready_in_n(name: &str, count: usize) -> &str {
|
||||
let mut remaining = count as isize;
|
||||
poll_fn(|_| {
|
||||
assert!(remaining >= 0);
|
||||
if remaining == 0 {
|
||||
Poll::Ready(name)
|
||||
} else {
|
||||
remaining -= 1;
|
||||
Poll::Pending
|
||||
}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cancel_future() {
|
||||
let cancel_now = CancelHandle::new_rc();
|
||||
let cancel_at_0 = CancelHandle::new_rc();
|
||||
let cancel_at_1 = CancelHandle::new_rc();
|
||||
let cancel_at_4 = CancelHandle::new_rc();
|
||||
let cancel_never = CancelHandle::new_rc();
|
||||
|
||||
cancel_now.cancel();
|
||||
|
||||
let mut futures = vec![
|
||||
box_fused(ready("A").or_cancel(&cancel_now)),
|
||||
box_fused(ready("B").or_cancel(&cancel_at_0)),
|
||||
box_fused(ready("C").or_cancel(&cancel_at_1)),
|
||||
box_fused(
|
||||
ready_in_n("D", 0)
|
||||
.or_cancel(&cancel_never)
|
||||
.try_or_cancel(&cancel_now),
|
||||
),
|
||||
box_fused(
|
||||
ready_in_n("E", 1)
|
||||
.or_cancel(&cancel_at_1)
|
||||
.try_or_cancel(&cancel_at_1),
|
||||
),
|
||||
box_fused(ready_in_n("F", 2).or_cancel(&cancel_at_1)),
|
||||
box_fused(ready_in_n("G", 3).or_cancel(&cancel_at_4)),
|
||||
box_fused(ready_in_n("H", 4).or_cancel(&cancel_at_4)),
|
||||
box_fused(ready_in_n("I", 5).or_cancel(&cancel_at_4)),
|
||||
box_fused(ready_in_n("J", 5).map(Ok)),
|
||||
box_fused(ready_in_n("K", 5).or_cancel(cancel_never)),
|
||||
];
|
||||
|
||||
let mut cx = Context::from_waker(noop_waker_ref());
|
||||
|
||||
for i in 0..=5 {
|
||||
match i {
|
||||
0 => cancel_at_0.cancel(),
|
||||
1 => cancel_at_1.cancel(),
|
||||
4 => cancel_at_4.cancel(),
|
||||
2 | 3 | 5 => {}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
let results = futures
|
||||
.iter_mut()
|
||||
.filter(|fut| !fut.is_terminated())
|
||||
.filter_map(|fut| match fut.poll_unpin(&mut cx) {
|
||||
Poll::Pending => None,
|
||||
Poll::Ready(res) => Some(res),
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
match i {
|
||||
0 => assert_eq!(
|
||||
results,
|
||||
[Err(Canceled), Err(Canceled), Ok("C"), Err(Canceled)]
|
||||
),
|
||||
1 => assert_eq!(results, [Err(Canceled), Err(Canceled)]),
|
||||
2 => assert_eq!(results, []),
|
||||
3 => assert_eq!(results, [Ok("G")]),
|
||||
4 => assert_eq!(results, [Err(Canceled), Err(Canceled)]),
|
||||
5 => assert_eq!(results, [Ok("J"), Ok("K")]),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
assert!(!futures.into_iter().any(|fut| !fut.is_terminated()));
|
||||
|
||||
let cancel_handles = [cancel_now, cancel_at_0, cancel_at_1, cancel_at_4];
|
||||
assert!(!cancel_handles.iter().any(|c| !c.is_canceled()));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn cancel_try_future() {
|
||||
{
|
||||
// Cancel a spawned task before it actually runs.
|
||||
let cancel_handle = Rc::new(CancelHandle::new());
|
||||
let future = spawn(async { panic!("the task should not be spawned") })
|
||||
.map_err(Error::from)
|
||||
.try_or_cancel(&cancel_handle);
|
||||
cancel_handle.cancel();
|
||||
let error = future.await.unwrap_err();
|
||||
assert!(error.downcast_ref::<Canceled>().is_some());
|
||||
assert_eq!(error.to_string().as_str(), "operation canceled");
|
||||
}
|
||||
|
||||
{
|
||||
// Cancel a network I/O future right after polling it.
|
||||
let cancel_handle = Rc::new(CancelHandle::new());
|
||||
let result = loop {
|
||||
select! {
|
||||
r = TcpStream::connect("1.2.3.4:12345")
|
||||
.try_or_cancel(&cancel_handle) => break r,
|
||||
default => cancel_handle.cancel(),
|
||||
};
|
||||
};
|
||||
let error = result.unwrap_err();
|
||||
assert_eq!(error.kind(), io::ErrorKind::Interrupted);
|
||||
assert_eq!(error.to_string().as_str(), "operation canceled");
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn future_cancels_itself_before_completion() {
|
||||
// A future cancels itself before it reaches completion. This future should
|
||||
// indeed get canceled and should not be polled again.
|
||||
let cancel_handle = CancelHandle::new_rc();
|
||||
let result = async {
|
||||
cancel_handle.cancel();
|
||||
yield_now().await;
|
||||
unreachable!();
|
||||
}
|
||||
.or_cancel(&cancel_handle)
|
||||
.await;
|
||||
assert_eq!(result.unwrap_err(), Canceled);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn future_cancels_itself_and_hangs() {
|
||||
// A future cancels itself, after which it returns `Poll::Pending` without
|
||||
// setting up a waker that would allow it to make progress towards
|
||||
// completion. Nevertheless, the `Cancelable` wrapper future must finish.
|
||||
let cancel_handle = CancelHandle::new_rc();
|
||||
let result = async {
|
||||
yield_now().await;
|
||||
cancel_handle.cancel();
|
||||
pending!();
|
||||
unreachable!();
|
||||
}
|
||||
.or_cancel(&cancel_handle)
|
||||
.await;
|
||||
assert_eq!(result.unwrap_err(), Canceled);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn future_cancels_itself_and_completes() {
|
||||
// A TryFuture attempts to cancel itself while it is getting polled, and
|
||||
// yields a result from the very same `poll()` call. Because this future
|
||||
// actually reaches completion, the attempted cancellation has no effect.
|
||||
let cancel_handle = CancelHandle::new_rc();
|
||||
let result = async {
|
||||
yield_now().await;
|
||||
cancel_handle.cancel();
|
||||
Ok::<_, io::Error>("done")
|
||||
}
|
||||
.try_or_cancel(&cancel_handle)
|
||||
.await;
|
||||
assert_eq!(result.unwrap(), "done");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cancel_handle_pinning() {
|
||||
let mut cancel_handle = CancelHandle::new_rc();
|
||||
|
||||
// There is only one reference to `cancel_handle`, so `Rc::get_mut()` should
|
||||
// succeed.
|
||||
assert!(Rc::get_mut(&mut cancel_handle).is_some());
|
||||
|
||||
let mut future = pending::<Never>().or_cancel(&cancel_handle);
|
||||
// SAFETY: `Cancelable` pins the future
|
||||
let future = unsafe { Pin::new_unchecked(&mut future) };
|
||||
|
||||
// There are two `Rc<CancelHandle>` references now, so this fails.
|
||||
assert!(Rc::get_mut(&mut cancel_handle).is_none());
|
||||
|
||||
let mut cx = Context::from_waker(noop_waker_ref());
|
||||
assert!(future.poll(&mut cx).is_pending());
|
||||
|
||||
// Polling `future` has established a link between the future and
|
||||
// `cancel_handle`, so both values should be pinned at this point.
|
||||
assert!(Rc::get_mut(&mut cancel_handle).is_none());
|
||||
|
||||
cancel_handle.cancel();
|
||||
|
||||
// Canceling or dropping the associated future(s) unlinks them from the
|
||||
// cancel handle, therefore `cancel_handle` can now safely be moved again.
|
||||
assert!(Rc::get_mut(&mut cancel_handle).is_some());
|
||||
}
|
||||
}
|
|
@ -1,780 +0,0 @@
|
|||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
||||
|
||||
use std::any::type_name;
|
||||
use std::any::Any;
|
||||
use std::borrow::Borrow;
|
||||
use std::cell::Cell;
|
||||
use std::cell::UnsafeCell;
|
||||
use std::collections::VecDeque;
|
||||
use std::fmt;
|
||||
use std::fmt::Debug;
|
||||
use std::fmt::Formatter;
|
||||
use std::ops::Deref;
|
||||
use std::rc::Rc;
|
||||
|
||||
use self::internal as i;
|
||||
|
||||
pub type AsyncRef<T> = i::AsyncBorrowImpl<T, i::Shared>;
|
||||
pub type AsyncMut<T> = i::AsyncBorrowImpl<T, i::Exclusive>;
|
||||
|
||||
pub type AsyncRefFuture<T> = i::AsyncBorrowFutureImpl<T, i::Shared>;
|
||||
pub type AsyncMutFuture<T> = i::AsyncBorrowFutureImpl<T, i::Exclusive>;
|
||||
|
||||
pub struct AsyncRefCell<T> {
|
||||
value: UnsafeCell<T>,
|
||||
borrow_count: Cell<i::BorrowCount>,
|
||||
waiters: Cell<VecDeque<Option<i::Waiter>>>,
|
||||
turn: Cell<usize>,
|
||||
}
|
||||
|
||||
impl<T: 'static> AsyncRefCell<T> {
|
||||
/// Create a new `AsyncRefCell` that encapsulates the specified value.
|
||||
/// Note that in order to borrow the inner value, the `AsyncRefCell`
|
||||
/// needs to be wrapped in an `Rc` or an `RcRef`. These can be created
|
||||
/// either manually, or by using the convenience method
|
||||
/// `AsyncRefCell::new_rc()`.
|
||||
pub fn new(value: T) -> Self {
|
||||
Self {
|
||||
value: UnsafeCell::new(value),
|
||||
borrow_count: Default::default(),
|
||||
waiters: Default::default(),
|
||||
turn: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_rc(value: T) -> Rc<Self> {
|
||||
Rc::new(Self::new(value))
|
||||
}
|
||||
|
||||
pub fn as_ptr(&self) -> *mut T {
|
||||
self.value.get()
|
||||
}
|
||||
|
||||
pub fn into_inner(self) -> T {
|
||||
assert!(self.borrow_count.get().is_empty());
|
||||
self.value.into_inner()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Debug for AsyncRefCell<T> {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
write!(f, "AsyncRefCell<{}>", type_name::<T>())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Default + 'static> Default for AsyncRefCell<T> {
|
||||
fn default() -> Self {
|
||||
Self::new(Default::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Default + 'static> AsyncRefCell<T> {
|
||||
pub fn default_rc() -> Rc<Self> {
|
||||
Rc::new(Default::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: 'static> From<T> for AsyncRefCell<T> {
|
||||
fn from(value: T) -> Self {
|
||||
Self::new(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> AsyncRefCell<T> {
|
||||
pub fn borrow(self: &Rc<Self>) -> AsyncRefFuture<T> {
|
||||
AsyncRefFuture::new(self)
|
||||
}
|
||||
|
||||
pub fn borrow_mut(self: &Rc<Self>) -> AsyncMutFuture<T> {
|
||||
AsyncMutFuture::new(self)
|
||||
}
|
||||
|
||||
pub fn try_borrow(self: &Rc<Self>) -> Option<AsyncRef<T>> {
|
||||
Self::borrow_sync(self)
|
||||
}
|
||||
|
||||
pub fn try_borrow_mut(self: &Rc<Self>) -> Option<AsyncMut<T>> {
|
||||
Self::borrow_sync(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> RcRef<AsyncRefCell<T>> {
|
||||
pub fn borrow(&self) -> AsyncRefFuture<T> {
|
||||
AsyncRefFuture::new(self)
|
||||
}
|
||||
|
||||
pub fn borrow_mut(&self) -> AsyncMutFuture<T> {
|
||||
AsyncMutFuture::new(self)
|
||||
}
|
||||
|
||||
pub fn try_borrow(&self) -> Option<AsyncRef<T>> {
|
||||
AsyncRefCell::<T>::borrow_sync(self)
|
||||
}
|
||||
|
||||
pub fn try_borrow_mut(&self) -> Option<AsyncMut<T>> {
|
||||
AsyncRefCell::<T>::borrow_sync(self)
|
||||
}
|
||||
}
|
||||
|
||||
/// An `RcRef` encapsulates a reference counted pointer, just like a regular
|
||||
/// `std::rc::Rc`. However, unlike a regular `Rc`, it can be remapped so that
|
||||
/// it dereferences to any value that's reachable through the reference-counted
|
||||
/// pointer. This is achieved through the associated method, `RcRef::map()`,
|
||||
/// similar to how `std::cell::Ref::map()` works. Example:
|
||||
///
|
||||
/// ```rust
|
||||
/// # use std::rc::Rc;
|
||||
/// # use deno_core::RcRef;
|
||||
///
|
||||
/// struct Stuff {
|
||||
/// foo: u32,
|
||||
/// bar: String,
|
||||
/// }
|
||||
///
|
||||
/// let stuff_rc = Rc::new(Stuff {
|
||||
/// foo: 42,
|
||||
/// bar: "hello".to_owned(),
|
||||
/// });
|
||||
///
|
||||
/// // `foo_rc` and `bar_rc` dereference to different types, however
|
||||
/// // they share a reference count.
|
||||
/// let foo_rc: RcRef<u32> = RcRef::map(stuff_rc.clone(), |v| &v.foo);
|
||||
/// let bar_rc: RcRef<String> = RcRef::map(stuff_rc, |v| &v.bar);
|
||||
/// ```
|
||||
#[derive(Debug)]
|
||||
pub struct RcRef<T> {
|
||||
rc: Rc<dyn Any>,
|
||||
value: *const T,
|
||||
}
|
||||
|
||||
impl<T: 'static> RcRef<T> {
|
||||
pub fn new(value: T) -> Self {
|
||||
Self::from(Rc::new(value))
|
||||
}
|
||||
|
||||
pub fn map<S: 'static, R: RcLike<S>, F: FnOnce(&S) -> &T>(
|
||||
source: R,
|
||||
map_fn: F,
|
||||
) -> RcRef<T> {
|
||||
let RcRef::<S> { rc, value } = source.into();
|
||||
// TODO(piscisaureus): safety comment
|
||||
#[allow(clippy::undocumented_unsafe_blocks)]
|
||||
let value = map_fn(unsafe { &*value });
|
||||
RcRef { rc, value }
|
||||
}
|
||||
|
||||
pub(crate) fn split(rc_ref: &Self) -> (&T, &Rc<dyn Any>) {
|
||||
let &Self { ref rc, value } = rc_ref;
|
||||
// TODO(piscisaureus): safety comment
|
||||
#[allow(clippy::undocumented_unsafe_blocks)]
|
||||
(unsafe { &*value }, rc)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Default + 'static> Default for RcRef<T> {
|
||||
fn default() -> Self {
|
||||
Self::new(Default::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Clone for RcRef<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
rc: self.rc.clone(),
|
||||
value: self.value,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: 'static> From<&RcRef<T>> for RcRef<T> {
|
||||
fn from(rc_ref: &RcRef<T>) -> Self {
|
||||
rc_ref.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: 'static> From<Rc<T>> for RcRef<T> {
|
||||
fn from(rc: Rc<T>) -> Self {
|
||||
Self {
|
||||
value: &*rc,
|
||||
rc: rc as Rc<_>,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: 'static> From<&Rc<T>> for RcRef<T> {
|
||||
fn from(rc: &Rc<T>) -> Self {
|
||||
rc.clone().into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Deref for RcRef<T> {
|
||||
type Target = T;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
// TODO(piscisaureus): safety comment
|
||||
#[allow(clippy::undocumented_unsafe_blocks)]
|
||||
unsafe {
|
||||
&*self.value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Borrow<T> for RcRef<T> {
|
||||
fn borrow(&self) -> &T {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> AsRef<T> for RcRef<T> {
|
||||
fn as_ref(&self) -> &T {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// The `RcLike` trait provides an abstraction over `std::rc::Rc` and `RcRef`,
|
||||
/// so that applicable methods can operate on either type.
|
||||
pub trait RcLike<T>: AsRef<T> + Into<RcRef<T>> {}
|
||||
|
||||
impl<T: 'static> RcLike<T> for Rc<T> {}
|
||||
impl<T: 'static> RcLike<T> for RcRef<T> {}
|
||||
impl<T: 'static> RcLike<T> for &Rc<T> {}
|
||||
impl<T: 'static> RcLike<T> for &RcRef<T> {}
|
||||
|
||||
mod internal {
|
||||
use super::AsyncRefCell;
|
||||
use super::RcLike;
|
||||
use super::RcRef;
|
||||
use futures::future::Future;
|
||||
use futures::ready;
|
||||
use futures::task::Context;
|
||||
use futures::task::Poll;
|
||||
use futures::task::Waker;
|
||||
use std::borrow::Borrow;
|
||||
use std::borrow::BorrowMut;
|
||||
use std::fmt::Debug;
|
||||
use std::marker::PhantomData;
|
||||
use std::ops::Deref;
|
||||
use std::ops::DerefMut;
|
||||
use std::pin::Pin;
|
||||
|
||||
impl<T> AsyncRefCell<T> {
|
||||
/// Borrow the cell's contents synchronously without creating an
|
||||
/// intermediate future. If the cell has already been borrowed and either
|
||||
/// the existing or the requested borrow is exclusive, this function returns
|
||||
/// `None`.
|
||||
pub fn borrow_sync<M: BorrowModeTrait, R: RcLike<AsyncRefCell<T>>>(
|
||||
cell: R,
|
||||
) -> Option<AsyncBorrowImpl<T, M>> {
|
||||
let cell_ref = cell.as_ref();
|
||||
// Don't allow synchronous borrows to cut in line; if there are any
|
||||
// enqueued waiters, return `None`, even if the current borrow is a shared
|
||||
// one and the requested borrow is too.
|
||||
// TODO(piscisaureus): safety comment
|
||||
#[allow(clippy::undocumented_unsafe_blocks)]
|
||||
let waiters = unsafe { &mut *cell_ref.waiters.as_ptr() };
|
||||
if waiters.is_empty() {
|
||||
// There are no enqueued waiters, but it is still possible that the cell
|
||||
// is currently borrowed. If there are no current borrows, or both the
|
||||
// existing and requested ones are shared, `try_add()` returns the
|
||||
// adjusted borrow count.
|
||||
let new_borrow_count =
|
||||
cell_ref.borrow_count.get().try_add(M::borrow_mode())?;
|
||||
cell_ref.borrow_count.set(new_borrow_count);
|
||||
Some(AsyncBorrowImpl::<T, M>::new(cell.into()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn drop_borrow<M: BorrowModeTrait>(&self) {
|
||||
let new_borrow_count = self.borrow_count.get().remove(M::borrow_mode());
|
||||
self.borrow_count.set(new_borrow_count);
|
||||
|
||||
if new_borrow_count.is_empty() {
|
||||
self.wake_waiters()
|
||||
}
|
||||
}
|
||||
|
||||
fn create_waiter<M: BorrowModeTrait>(&self) -> usize {
|
||||
let waiter = Waiter::new(M::borrow_mode());
|
||||
let turn = self.turn.get();
|
||||
let index = {
|
||||
// TODO(piscisaureus): safety comment
|
||||
#[allow(clippy::undocumented_unsafe_blocks)]
|
||||
let waiters = unsafe { &mut *self.waiters.as_ptr() };
|
||||
waiters.push_back(Some(waiter));
|
||||
waiters.len() - 1
|
||||
};
|
||||
if index == 0 {
|
||||
// SAFETY: the `waiters` reference used above *must* be dropped here.
|
||||
self.wake_waiters()
|
||||
}
|
||||
// Return the new waiter's id.
|
||||
turn + index
|
||||
}
|
||||
|
||||
fn poll_waiter<M: BorrowModeTrait>(
|
||||
&self,
|
||||
id: usize,
|
||||
cx: &mut Context,
|
||||
) -> Poll<()> {
|
||||
let borrow_count = self.borrow_count.get();
|
||||
let turn = self.turn.get();
|
||||
if id < turn {
|
||||
// This waiter made it to the front of the line; we reserved a borrow
|
||||
// for it, woke its Waker, and removed the waiter from the queue.
|
||||
// Assertion: BorrowCount::remove() will panic if `mode` is incorrect.
|
||||
let _ = borrow_count.remove(M::borrow_mode());
|
||||
Poll::Ready(())
|
||||
} else {
|
||||
// This waiter is still in line and has not yet been woken.
|
||||
// TODO(piscisaureus): safety comment
|
||||
#[allow(clippy::undocumented_unsafe_blocks)]
|
||||
let waiters = unsafe { &mut *self.waiters.as_ptr() };
|
||||
// Sanity check: id cannot be higher than the last queue element.
|
||||
assert!(id < turn + waiters.len());
|
||||
// Sanity check: since we always call wake_waiters() when the queue head
|
||||
// is updated, it should be impossible to add it to the current borrow.
|
||||
assert!(id > turn || borrow_count.try_add(M::borrow_mode()).is_none());
|
||||
// Save or update the waiter's Waker.
|
||||
let waiter_mut = waiters[id - turn].as_mut().unwrap();
|
||||
waiter_mut.set_waker(cx.waker());
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
|
||||
fn wake_waiters(&self) {
|
||||
let mut borrow_count = self.borrow_count.get();
|
||||
// TODO(piscisaureus): safety comment
|
||||
#[allow(clippy::undocumented_unsafe_blocks)]
|
||||
let waiters = unsafe { &mut *self.waiters.as_ptr() };
|
||||
let mut turn = self.turn.get();
|
||||
|
||||
loop {
|
||||
let waiter_entry = match waiters.front().map(Option::as_ref) {
|
||||
None => break, // Queue empty.
|
||||
Some(w) => w,
|
||||
};
|
||||
let borrow_mode = match waiter_entry {
|
||||
None => {
|
||||
// Queue contains a hole. This happens when a Waiter is dropped
|
||||
// before it makes it to the front of the queue.
|
||||
waiters.pop_front();
|
||||
turn += 1;
|
||||
continue;
|
||||
}
|
||||
Some(waiter) => waiter.borrow_mode(),
|
||||
};
|
||||
// See if the waiter at the front of the queue can borrow the cell's
|
||||
// value now. If it does, `try_add()` returns the new borrow count,
|
||||
// effectively "reserving" the borrow until the associated
|
||||
// AsyncBorrowFutureImpl future gets polled and produces the actual
|
||||
// borrow.
|
||||
borrow_count = match borrow_count.try_add(borrow_mode) {
|
||||
None => break, // Can't borrow yet.
|
||||
Some(b) => b,
|
||||
};
|
||||
// Drop from queue.
|
||||
let mut waiter = waiters.pop_front().unwrap().unwrap();
|
||||
turn += 1;
|
||||
// Wake this waiter, so the AsyncBorrowFutureImpl future gets polled.
|
||||
if let Some(waker) = waiter.take_waker() {
|
||||
waker.wake()
|
||||
}
|
||||
}
|
||||
// Save updated counters.
|
||||
self.borrow_count.set(borrow_count);
|
||||
self.turn.set(turn);
|
||||
}
|
||||
|
||||
fn drop_waiter<M: BorrowModeTrait>(&self, id: usize) {
|
||||
let turn = self.turn.get();
|
||||
if id < turn {
|
||||
// We already made a borrow count reservation for this waiter but the
|
||||
// borrow will never be picked up and consequently, never dropped.
|
||||
// Therefore, call the borrow drop handler here.
|
||||
self.drop_borrow::<M>();
|
||||
} else {
|
||||
// This waiter is still in the queue, take it out and leave a "hole".
|
||||
// TODO(piscisaureus): safety comment
|
||||
#[allow(clippy::undocumented_unsafe_blocks)]
|
||||
let waiters = unsafe { &mut *self.waiters.as_ptr() };
|
||||
waiters[id - turn].take().unwrap();
|
||||
}
|
||||
|
||||
if id == turn {
|
||||
// Since the first entry in the waiter queue was touched we have to
|
||||
// reprocess the waiter queue.
|
||||
self.wake_waiters()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AsyncBorrowFutureImpl<T: 'static, M: BorrowModeTrait> {
|
||||
cell: Option<RcRef<AsyncRefCell<T>>>,
|
||||
id: usize,
|
||||
_phantom: PhantomData<M>,
|
||||
}
|
||||
|
||||
impl<T, M: BorrowModeTrait> AsyncBorrowFutureImpl<T, M> {
|
||||
pub fn new<R: RcLike<AsyncRefCell<T>>>(cell: R) -> Self {
|
||||
Self {
|
||||
id: cell.as_ref().create_waiter::<M>(),
|
||||
cell: Some(cell.into()),
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: 'static, M: BorrowModeTrait> Future for AsyncBorrowFutureImpl<T, M> {
|
||||
type Output = AsyncBorrowImpl<T, M>;
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
ready!(self.cell.as_ref().unwrap().poll_waiter::<M>(self.id, cx));
|
||||
// TODO(piscisaureus): safety comment
|
||||
#[allow(clippy::undocumented_unsafe_blocks)]
|
||||
let self_mut = unsafe { Pin::get_unchecked_mut(self) };
|
||||
let cell = self_mut.cell.take().unwrap();
|
||||
Poll::Ready(AsyncBorrowImpl::<T, M>::new(cell))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, M: BorrowModeTrait> Drop for AsyncBorrowFutureImpl<T, M> {
|
||||
fn drop(&mut self) {
|
||||
// The expected mode of operation is that this future gets polled until it
|
||||
// is ready and yields a value of type `AsyncBorrowImpl`, which has a drop
|
||||
// handler that adjusts the `AsyncRefCell` borrow counter. However if the
|
||||
// `cell` field still holds a value at this point, it means that the
|
||||
// future was never polled to completion and no `AsyncBorrowImpl` was ever
|
||||
// created, so we have to adjust the borrow count here.
|
||||
if let Some(cell) = self.cell.take() {
|
||||
cell.drop_waiter::<M>(self.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AsyncBorrowImpl<T: 'static, M: BorrowModeTrait> {
|
||||
cell: RcRef<AsyncRefCell<T>>,
|
||||
_phantom: PhantomData<M>,
|
||||
}
|
||||
|
||||
impl<T, M: BorrowModeTrait> AsyncBorrowImpl<T, M> {
|
||||
fn new(cell: RcRef<AsyncRefCell<T>>) -> Self {
|
||||
Self {
|
||||
cell,
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, M: BorrowModeTrait> Deref for AsyncBorrowImpl<T, M> {
|
||||
type Target = T;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
// TODO(piscisaureus): safety comment
|
||||
#[allow(clippy::undocumented_unsafe_blocks)]
|
||||
unsafe {
|
||||
&*self.cell.as_ptr()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, M: BorrowModeTrait> Borrow<T> for AsyncBorrowImpl<T, M> {
|
||||
fn borrow(&self) -> &T {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, M: BorrowModeTrait> AsRef<T> for AsyncBorrowImpl<T, M> {
|
||||
fn as_ref(&self) -> &T {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> DerefMut for AsyncBorrowImpl<T, Exclusive> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
// TODO(piscisaureus): safety comment
|
||||
#[allow(clippy::undocumented_unsafe_blocks)]
|
||||
unsafe {
|
||||
&mut *self.cell.as_ptr()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> BorrowMut<T> for AsyncBorrowImpl<T, Exclusive> {
|
||||
fn borrow_mut(&mut self) -> &mut T {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> AsMut<T> for AsyncBorrowImpl<T, Exclusive> {
|
||||
fn as_mut(&mut self) -> &mut T {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, M: BorrowModeTrait> Drop for AsyncBorrowImpl<T, M> {
|
||||
fn drop(&mut self) {
|
||||
self.cell.drop_borrow::<M>()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||
pub enum BorrowMode {
|
||||
Shared,
|
||||
Exclusive,
|
||||
}
|
||||
|
||||
pub trait BorrowModeTrait: Copy {
|
||||
fn borrow_mode() -> BorrowMode;
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct Shared;
|
||||
|
||||
impl BorrowModeTrait for Shared {
|
||||
fn borrow_mode() -> BorrowMode {
|
||||
BorrowMode::Shared
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct Exclusive;
|
||||
|
||||
impl BorrowModeTrait for Exclusive {
|
||||
fn borrow_mode() -> BorrowMode {
|
||||
BorrowMode::Exclusive
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||
pub enum BorrowCount {
|
||||
Shared(usize),
|
||||
Exclusive,
|
||||
}
|
||||
|
||||
impl Default for BorrowCount {
|
||||
fn default() -> Self {
|
||||
Self::Shared(0)
|
||||
}
|
||||
}
|
||||
|
||||
impl BorrowCount {
|
||||
pub fn is_empty(self) -> bool {
|
||||
matches!(self, BorrowCount::Shared(0))
|
||||
}
|
||||
|
||||
pub fn try_add(self, mode: BorrowMode) -> Option<BorrowCount> {
|
||||
match (self, mode) {
|
||||
(BorrowCount::Shared(refs), BorrowMode::Shared) => {
|
||||
Some(BorrowCount::Shared(refs + 1))
|
||||
}
|
||||
(BorrowCount::Shared(0), BorrowMode::Exclusive) => {
|
||||
Some(BorrowCount::Exclusive)
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn add(self, mode: BorrowMode) -> BorrowCount {
|
||||
match self.try_add(mode) {
|
||||
Some(value) => value,
|
||||
None => panic!("Can't add {mode:?} to {self:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_remove(self, mode: BorrowMode) -> Option<BorrowCount> {
|
||||
match (self, mode) {
|
||||
(BorrowCount::Shared(refs), BorrowMode::Shared) if refs > 0 => {
|
||||
Some(BorrowCount::Shared(refs - 1))
|
||||
}
|
||||
(BorrowCount::Exclusive, BorrowMode::Exclusive) => {
|
||||
Some(BorrowCount::Shared(0))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remove(self, mode: BorrowMode) -> BorrowCount {
|
||||
match self.try_remove(mode) {
|
||||
Some(value) => value,
|
||||
None => panic!("Can't remove {mode:?} from {self:?}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The `waiters` queue that is associated with an individual `AsyncRefCell`
|
||||
/// contains elements of the `Waiter` type.
|
||||
pub struct Waiter {
|
||||
borrow_mode: BorrowMode,
|
||||
waker: Option<Waker>,
|
||||
}
|
||||
|
||||
impl Waiter {
|
||||
pub fn new(borrow_mode: BorrowMode) -> Self {
|
||||
Self {
|
||||
borrow_mode,
|
||||
waker: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn borrow_mode(&self) -> BorrowMode {
|
||||
self.borrow_mode
|
||||
}
|
||||
|
||||
pub fn set_waker(&mut self, new_waker: &Waker) {
|
||||
if self
|
||||
.waker
|
||||
.as_ref()
|
||||
.filter(|waker| waker.will_wake(new_waker))
|
||||
.is_none()
|
||||
{
|
||||
self.waker.replace(new_waker.clone());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn take_waker(&mut self) -> Option<Waker> {
|
||||
self.waker.take()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[derive(Default)]
|
||||
struct Thing {
|
||||
touch_count: usize,
|
||||
_private: (),
|
||||
}
|
||||
|
||||
impl Thing {
|
||||
pub fn look(&self) -> usize {
|
||||
self.touch_count
|
||||
}
|
||||
|
||||
pub fn touch(&mut self) -> usize {
|
||||
self.touch_count += 1;
|
||||
self.touch_count
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn async_ref_cell_borrow() {
|
||||
let cell = AsyncRefCell::<Thing>::default_rc();
|
||||
|
||||
let fut1 = cell.borrow();
|
||||
let fut2 = cell.borrow_mut();
|
||||
let fut3 = cell.borrow();
|
||||
let fut4 = cell.borrow();
|
||||
let fut5 = cell.borrow();
|
||||
let fut6 = cell.borrow();
|
||||
let fut7 = cell.borrow_mut();
|
||||
let fut8 = cell.borrow();
|
||||
|
||||
// The `try_borrow` and `try_borrow_mut` methods should always return `None`
|
||||
// if there's a queue of async borrowers.
|
||||
assert!(cell.try_borrow().is_none());
|
||||
assert!(cell.try_borrow_mut().is_none());
|
||||
|
||||
assert_eq!(fut1.await.look(), 0);
|
||||
|
||||
assert_eq!(fut2.await.touch(), 1);
|
||||
|
||||
{
|
||||
let ref5 = fut5.await;
|
||||
let ref4 = fut4.await;
|
||||
let ref3 = fut3.await;
|
||||
let ref6 = fut6.await;
|
||||
assert_eq!(ref3.look(), 1);
|
||||
assert_eq!(ref4.look(), 1);
|
||||
assert_eq!(ref5.look(), 1);
|
||||
assert_eq!(ref6.look(), 1);
|
||||
}
|
||||
|
||||
{
|
||||
let mut ref7 = fut7.await;
|
||||
assert_eq!(ref7.look(), 1);
|
||||
assert_eq!(ref7.touch(), 2);
|
||||
}
|
||||
|
||||
{
|
||||
let ref8 = fut8.await;
|
||||
assert_eq!(ref8.look(), 2);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn async_ref_cell_try_borrow() {
|
||||
let cell = AsyncRefCell::<Thing>::default_rc();
|
||||
|
||||
{
|
||||
let ref1 = cell.try_borrow().unwrap();
|
||||
assert_eq!(ref1.look(), 0);
|
||||
assert!(cell.try_borrow_mut().is_none());
|
||||
}
|
||||
|
||||
{
|
||||
let mut ref2 = cell.try_borrow_mut().unwrap();
|
||||
assert_eq!(ref2.touch(), 1);
|
||||
assert!(cell.try_borrow().is_none());
|
||||
assert!(cell.try_borrow_mut().is_none());
|
||||
}
|
||||
|
||||
{
|
||||
let ref3 = cell.try_borrow().unwrap();
|
||||
let ref4 = cell.try_borrow().unwrap();
|
||||
let ref5 = cell.try_borrow().unwrap();
|
||||
let ref6 = cell.try_borrow().unwrap();
|
||||
assert_eq!(ref3.look(), 1);
|
||||
assert_eq!(ref4.look(), 1);
|
||||
assert_eq!(ref5.look(), 1);
|
||||
assert_eq!(ref6.look(), 1);
|
||||
assert!(cell.try_borrow_mut().is_none());
|
||||
}
|
||||
|
||||
{
|
||||
let mut ref7 = cell.try_borrow_mut().unwrap();
|
||||
assert_eq!(ref7.look(), 1);
|
||||
assert_eq!(ref7.touch(), 2);
|
||||
assert!(cell.try_borrow().is_none());
|
||||
assert!(cell.try_borrow_mut().is_none());
|
||||
}
|
||||
|
||||
{
|
||||
let ref8 = cell.try_borrow().unwrap();
|
||||
assert_eq!(ref8.look(), 2);
|
||||
assert!(cell.try_borrow_mut().is_none());
|
||||
assert!(cell.try_borrow().is_some());
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct ThreeThings {
|
||||
pub thing1: AsyncRefCell<Thing>,
|
||||
pub thing2: AsyncRefCell<Thing>,
|
||||
pub thing3: AsyncRefCell<Thing>,
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn rc_ref_map() {
|
||||
let three_cells = Rc::new(ThreeThings::default());
|
||||
|
||||
let rc1 = RcRef::map(three_cells.clone(), |things| &things.thing1);
|
||||
let rc2 = RcRef::map(three_cells.clone(), |things| &things.thing2);
|
||||
let rc3 = RcRef::map(three_cells, |things| &things.thing3);
|
||||
|
||||
let mut ref1 = rc1.borrow_mut().await;
|
||||
let ref2 = rc2.borrow().await;
|
||||
let mut ref3 = rc3.borrow_mut().await;
|
||||
|
||||
assert_eq!(ref1.look(), 0);
|
||||
assert_eq!(ref3.touch(), 1);
|
||||
assert_eq!(ref1.touch(), 1);
|
||||
assert_eq!(ref2.look(), 0);
|
||||
assert_eq!(ref3.touch(), 2);
|
||||
assert_eq!(ref1.look(), 1);
|
||||
assert_eq!(ref1.touch(), 2);
|
||||
assert_eq!(ref3.touch(), 3);
|
||||
assert_eq!(ref1.touch(), 3);
|
||||
}
|
||||
}
|
719
core/error.rs
719
core/error.rs
|
@ -1,719 +0,0 @@
|
|||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::collections::HashSet;
|
||||
use std::fmt;
|
||||
use std::fmt::Debug;
|
||||
use std::fmt::Display;
|
||||
use std::fmt::Formatter;
|
||||
|
||||
use anyhow::Error;
|
||||
|
||||
use crate::runtime::JsRealm;
|
||||
use crate::runtime::JsRuntime;
|
||||
use crate::source_map::apply_source_map;
|
||||
use crate::source_map::get_source_line;
|
||||
use crate::url::Url;
|
||||
|
||||
/// A generic wrapper that can encapsulate any concrete error type.
|
||||
// TODO(ry) Deprecate AnyError and encourage deno_core::anyhow::Error instead.
|
||||
pub type AnyError = anyhow::Error;
|
||||
|
||||
pub type JsErrorCreateFn = dyn Fn(JsError) -> Error;
|
||||
pub type GetErrorClassFn = &'static dyn for<'e> Fn(&'e Error) -> &'static str;
|
||||
|
||||
/// Creates a new error with a caller-specified error class name and message.
|
||||
pub fn custom_error(
|
||||
class: &'static str,
|
||||
message: impl Into<Cow<'static, str>>,
|
||||
) -> Error {
|
||||
CustomError {
|
||||
class,
|
||||
message: message.into(),
|
||||
}
|
||||
.into()
|
||||
}
|
||||
|
||||
pub fn generic_error(message: impl Into<Cow<'static, str>>) -> Error {
|
||||
custom_error("Error", message)
|
||||
}
|
||||
|
||||
pub fn type_error(message: impl Into<Cow<'static, str>>) -> Error {
|
||||
custom_error("TypeError", message)
|
||||
}
|
||||
|
||||
pub fn range_error(message: impl Into<Cow<'static, str>>) -> Error {
|
||||
custom_error("RangeError", message)
|
||||
}
|
||||
|
||||
pub fn invalid_hostname(hostname: &str) -> Error {
|
||||
type_error(format!("Invalid hostname: '{hostname}'"))
|
||||
}
|
||||
|
||||
pub fn uri_error(message: impl Into<Cow<'static, str>>) -> Error {
|
||||
custom_error("URIError", message)
|
||||
}
|
||||
|
||||
pub fn bad_resource(message: impl Into<Cow<'static, str>>) -> Error {
|
||||
custom_error("BadResource", message)
|
||||
}
|
||||
|
||||
pub fn bad_resource_id() -> Error {
|
||||
custom_error("BadResource", "Bad resource ID")
|
||||
}
|
||||
|
||||
pub fn not_supported() -> Error {
|
||||
custom_error("NotSupported", "The operation is not supported")
|
||||
}
|
||||
|
||||
pub fn resource_unavailable() -> Error {
|
||||
custom_error(
|
||||
"Busy",
|
||||
"Resource is unavailable because it is in use by a promise",
|
||||
)
|
||||
}
|
||||
|
||||
/// A simple error type that lets the creator specify both the error message and
|
||||
/// the error class name. This type is private; externally it only ever appears
|
||||
/// wrapped in an `anyhow::Error`. To retrieve the error class name from a wrapped
|
||||
/// `CustomError`, use the function `get_custom_error_class()`.
|
||||
#[derive(Debug)]
|
||||
struct CustomError {
|
||||
class: &'static str,
|
||||
message: Cow<'static, str>,
|
||||
}
|
||||
|
||||
impl Display for CustomError {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
f.write_str(&self.message)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for CustomError {}
|
||||
|
||||
/// If this error was crated with `custom_error()`, return the specified error
|
||||
/// class name. In all other cases this function returns `None`.
|
||||
pub fn get_custom_error_class(error: &Error) -> Option<&'static str> {
|
||||
error.downcast_ref::<CustomError>().map(|e| e.class)
|
||||
}
|
||||
|
||||
pub fn to_v8_error<'a>(
|
||||
scope: &mut v8::HandleScope<'a>,
|
||||
get_class: GetErrorClassFn,
|
||||
error: &Error,
|
||||
) -> v8::Local<'a, v8::Value> {
|
||||
let tc_scope = &mut v8::TryCatch::new(scope);
|
||||
let cb = JsRealm::state_from_scope(tc_scope)
|
||||
.borrow()
|
||||
.js_build_custom_error_cb
|
||||
.clone()
|
||||
.expect("Custom error builder must be set");
|
||||
let cb = cb.open(tc_scope);
|
||||
let this = v8::undefined(tc_scope).into();
|
||||
let class = v8::String::new(tc_scope, get_class(error)).unwrap();
|
||||
let message = v8::String::new(tc_scope, &format!("{error:#}")).unwrap();
|
||||
let mut args = vec![class.into(), message.into()];
|
||||
if let Some(code) = crate::error_codes::get_error_code(error) {
|
||||
args.push(v8::String::new(tc_scope, code).unwrap().into());
|
||||
}
|
||||
let maybe_exception = cb.call(tc_scope, this, &args);
|
||||
|
||||
match maybe_exception {
|
||||
Some(exception) => exception,
|
||||
None => {
|
||||
let mut msg =
|
||||
"Custom error class must have a builder registered".to_string();
|
||||
if tc_scope.has_caught() {
|
||||
let e = tc_scope.exception().unwrap();
|
||||
let js_error = JsError::from_v8_exception(tc_scope, e);
|
||||
msg = format!("{}: {}", msg, js_error.exception_message);
|
||||
}
|
||||
panic!("{}", msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A `JsError` represents an exception coming from V8, with stack frames and
|
||||
/// line numbers. The deno_cli crate defines another `JsError` type, which wraps
|
||||
/// the one defined here, that adds source map support and colorful formatting.
|
||||
/// When updating this struct, also update errors_are_equal_without_cause() in
|
||||
/// fmt_error.rs.
|
||||
#[derive(Debug, PartialEq, Clone, serde::Deserialize, serde::Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct JsError {
|
||||
pub name: Option<String>,
|
||||
pub message: Option<String>,
|
||||
pub stack: Option<String>,
|
||||
pub cause: Option<Box<JsError>>,
|
||||
pub exception_message: String,
|
||||
pub frames: Vec<JsStackFrame>,
|
||||
pub source_line: Option<String>,
|
||||
pub source_line_frame_index: Option<usize>,
|
||||
pub aggregated: Option<Vec<JsError>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, serde::Deserialize, serde::Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct JsStackFrame {
|
||||
pub type_name: Option<String>,
|
||||
pub function_name: Option<String>,
|
||||
pub method_name: Option<String>,
|
||||
pub file_name: Option<String>,
|
||||
pub line_number: Option<i64>,
|
||||
pub column_number: Option<i64>,
|
||||
pub eval_origin: Option<String>,
|
||||
// Warning! isToplevel has inconsistent snake<>camel case, "typo" originates in v8:
|
||||
// https://source.chromium.org/search?q=isToplevel&sq=&ss=chromium%2Fchromium%2Fsrc:v8%2F
|
||||
#[serde(rename = "isToplevel")]
|
||||
pub is_top_level: Option<bool>,
|
||||
pub is_eval: bool,
|
||||
pub is_native: bool,
|
||||
pub is_constructor: bool,
|
||||
pub is_async: bool,
|
||||
pub is_promise_all: bool,
|
||||
pub promise_index: Option<i64>,
|
||||
}
|
||||
|
||||
impl JsStackFrame {
|
||||
pub fn from_location(
|
||||
file_name: Option<String>,
|
||||
line_number: Option<i64>,
|
||||
column_number: Option<i64>,
|
||||
) -> Self {
|
||||
Self {
|
||||
type_name: None,
|
||||
function_name: None,
|
||||
method_name: None,
|
||||
file_name,
|
||||
line_number,
|
||||
column_number,
|
||||
eval_origin: None,
|
||||
is_top_level: None,
|
||||
is_eval: false,
|
||||
is_native: false,
|
||||
is_constructor: false,
|
||||
is_async: false,
|
||||
is_promise_all: false,
|
||||
promise_index: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets the source mapped stack frame corresponding to the
|
||||
/// (script_resource_name, line_number, column_number) from a v8 message.
|
||||
/// For non-syntax errors, it should also correspond to the first stack frame.
|
||||
pub fn from_v8_message<'a>(
|
||||
scope: &'a mut v8::HandleScope,
|
||||
message: v8::Local<'a, v8::Message>,
|
||||
) -> Option<Self> {
|
||||
let f = message.get_script_resource_name(scope)?;
|
||||
let f: v8::Local<v8::String> = f.try_into().ok()?;
|
||||
let f = f.to_rust_string_lossy(scope);
|
||||
let l = message.get_line_number(scope)? as i64;
|
||||
// V8's column numbers are 0-based, we want 1-based.
|
||||
let c = message.get_start_column() as i64 + 1;
|
||||
let state_rc = JsRuntime::state_from(scope);
|
||||
let (getter, cache) = {
|
||||
let state = state_rc.borrow();
|
||||
(
|
||||
state.source_map_getter.clone(),
|
||||
state.source_map_cache.clone(),
|
||||
)
|
||||
};
|
||||
|
||||
if let Some(source_map_getter) = getter {
|
||||
let mut cache = cache.borrow_mut();
|
||||
let (f, l, c) =
|
||||
apply_source_map(f, l, c, &mut cache, &**source_map_getter);
|
||||
Some(JsStackFrame::from_location(Some(f), Some(l), Some(c)))
|
||||
} else {
|
||||
Some(JsStackFrame::from_location(Some(f), Some(l), Some(c)))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn maybe_format_location(&self) -> Option<String> {
|
||||
Some(format!(
|
||||
"{}:{}:{}",
|
||||
self.file_name.as_ref()?,
|
||||
self.line_number?,
|
||||
self.column_number?
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn get_property<'a>(
|
||||
scope: &mut v8::HandleScope<'a>,
|
||||
object: v8::Local<v8::Object>,
|
||||
key: &str,
|
||||
) -> Option<v8::Local<'a, v8::Value>> {
|
||||
let key = v8::String::new(scope, key).unwrap();
|
||||
object.get(scope, key.into())
|
||||
}
|
||||
|
||||
#[derive(Default, serde::Deserialize)]
|
||||
pub(crate) struct NativeJsError {
|
||||
pub name: Option<String>,
|
||||
pub message: Option<String>,
|
||||
// Warning! .stack is special so handled by itself
|
||||
// stack: Option<String>,
|
||||
}
|
||||
|
||||
impl JsError {
|
||||
pub fn from_v8_exception(
|
||||
scope: &mut v8::HandleScope,
|
||||
exception: v8::Local<v8::Value>,
|
||||
) -> Self {
|
||||
Self::inner_from_v8_exception(scope, exception, Default::default())
|
||||
}
|
||||
|
||||
pub fn from_v8_message<'a>(
|
||||
scope: &'a mut v8::HandleScope,
|
||||
msg: v8::Local<'a, v8::Message>,
|
||||
) -> Self {
|
||||
// Create a new HandleScope because we're creating a lot of new local
|
||||
// handles below.
|
||||
let scope = &mut v8::HandleScope::new(scope);
|
||||
|
||||
let exception_message = msg.get(scope).to_rust_string_lossy(scope);
|
||||
|
||||
// Convert them into Vec<JsStackFrame>
|
||||
let mut frames: Vec<JsStackFrame> = vec![];
|
||||
let mut source_line = None;
|
||||
let mut source_line_frame_index = None;
|
||||
|
||||
if let Some(stack_frame) = JsStackFrame::from_v8_message(scope, msg) {
|
||||
frames = vec![stack_frame];
|
||||
}
|
||||
{
|
||||
let state_rc = JsRuntime::state_from(scope);
|
||||
let (getter, cache) = {
|
||||
let state = state_rc.borrow();
|
||||
(
|
||||
state.source_map_getter.clone(),
|
||||
state.source_map_cache.clone(),
|
||||
)
|
||||
};
|
||||
if let Some(source_map_getter) = getter {
|
||||
let mut cache = cache.borrow_mut();
|
||||
for (i, frame) in frames.iter().enumerate() {
|
||||
if let (Some(file_name), Some(line_number)) =
|
||||
(&frame.file_name, frame.line_number)
|
||||
{
|
||||
if !file_name.trim_start_matches('[').starts_with("ext:") {
|
||||
source_line = get_source_line(
|
||||
file_name,
|
||||
line_number,
|
||||
&mut cache,
|
||||
&**source_map_getter,
|
||||
);
|
||||
source_line_frame_index = Some(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Self {
|
||||
name: None,
|
||||
message: None,
|
||||
exception_message,
|
||||
cause: None,
|
||||
source_line,
|
||||
source_line_frame_index,
|
||||
frames,
|
||||
stack: None,
|
||||
aggregated: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn inner_from_v8_exception<'a>(
|
||||
scope: &'a mut v8::HandleScope,
|
||||
exception: v8::Local<'a, v8::Value>,
|
||||
mut seen: HashSet<v8::Local<'a, v8::Object>>,
|
||||
) -> Self {
|
||||
// Create a new HandleScope because we're creating a lot of new local
|
||||
// handles below.
|
||||
let scope = &mut v8::HandleScope::new(scope);
|
||||
|
||||
let msg = v8::Exception::create_message(scope, exception);
|
||||
|
||||
let mut exception_message = None;
|
||||
let context_state_rc = JsRealm::state_from_scope(scope);
|
||||
|
||||
let js_format_exception_cb =
|
||||
context_state_rc.borrow().js_format_exception_cb.clone();
|
||||
if let Some(format_exception_cb) = js_format_exception_cb {
|
||||
let format_exception_cb = format_exception_cb.open(scope);
|
||||
let this = v8::undefined(scope).into();
|
||||
let formatted = format_exception_cb.call(scope, this, &[exception]);
|
||||
if let Some(formatted) = formatted {
|
||||
if formatted.is_string() {
|
||||
exception_message = Some(formatted.to_rust_string_lossy(scope));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if is_instance_of_error(scope, exception) {
|
||||
let v8_exception = exception;
|
||||
// The exception is a JS Error object.
|
||||
let exception: v8::Local<v8::Object> = exception.try_into().unwrap();
|
||||
let cause = get_property(scope, exception, "cause");
|
||||
let e: NativeJsError =
|
||||
serde_v8::from_v8(scope, exception.into()).unwrap_or_default();
|
||||
// Get the message by formatting error.name and error.message.
|
||||
let name = e.name.clone().unwrap_or_else(|| "Error".to_string());
|
||||
let message_prop = e.message.clone().unwrap_or_default();
|
||||
let exception_message = exception_message.unwrap_or_else(|| {
|
||||
if !name.is_empty() && !message_prop.is_empty() {
|
||||
format!("Uncaught {name}: {message_prop}")
|
||||
} else if !name.is_empty() {
|
||||
format!("Uncaught {name}")
|
||||
} else if !message_prop.is_empty() {
|
||||
format!("Uncaught {message_prop}")
|
||||
} else {
|
||||
"Uncaught".to_string()
|
||||
}
|
||||
});
|
||||
let cause = cause.and_then(|cause| {
|
||||
if cause.is_undefined() || seen.contains(&exception) {
|
||||
None
|
||||
} else {
|
||||
seen.insert(exception);
|
||||
Some(Box::new(JsError::inner_from_v8_exception(
|
||||
scope, cause, seen,
|
||||
)))
|
||||
}
|
||||
});
|
||||
|
||||
// Access error.stack to ensure that prepareStackTrace() has been called.
|
||||
// This should populate error.__callSiteEvals.
|
||||
let stack = get_property(scope, exception, "stack");
|
||||
let stack: Option<v8::Local<v8::String>> =
|
||||
stack.and_then(|s| s.try_into().ok());
|
||||
let stack = stack.map(|s| s.to_rust_string_lossy(scope));
|
||||
|
||||
// Read an array of structured frames from error.__callSiteEvals.
|
||||
let frames_v8 = get_property(scope, exception, "__callSiteEvals");
|
||||
// Ignore non-array values
|
||||
let frames_v8: Option<v8::Local<v8::Array>> =
|
||||
frames_v8.and_then(|a| a.try_into().ok());
|
||||
|
||||
// Convert them into Vec<JsStackFrame>
|
||||
let mut frames: Vec<JsStackFrame> = match frames_v8 {
|
||||
Some(frames_v8) => serde_v8::from_v8(scope, frames_v8.into()).unwrap(),
|
||||
None => vec![],
|
||||
};
|
||||
let mut source_line = None;
|
||||
let mut source_line_frame_index = None;
|
||||
|
||||
// When the stack frame array is empty, but the source location given by
|
||||
// (script_resource_name, line_number, start_column + 1) exists, this is
|
||||
// likely a syntax error. For the sake of formatting we treat it like it
|
||||
// was given as a single stack frame.
|
||||
if frames.is_empty() {
|
||||
if let Some(stack_frame) = JsStackFrame::from_v8_message(scope, msg) {
|
||||
frames = vec![stack_frame];
|
||||
}
|
||||
}
|
||||
{
|
||||
let state_rc = JsRuntime::state_from(scope);
|
||||
let (getter, cache) = {
|
||||
let state = state_rc.borrow();
|
||||
(
|
||||
state.source_map_getter.clone(),
|
||||
state.source_map_cache.clone(),
|
||||
)
|
||||
};
|
||||
if let Some(source_map_getter) = getter {
|
||||
let mut cache = cache.borrow_mut();
|
||||
|
||||
for (i, frame) in frames.iter().enumerate() {
|
||||
if let (Some(file_name), Some(line_number)) =
|
||||
(&frame.file_name, frame.line_number)
|
||||
{
|
||||
if !file_name.trim_start_matches('[').starts_with("ext:") {
|
||||
source_line = get_source_line(
|
||||
file_name,
|
||||
line_number,
|
||||
&mut cache,
|
||||
&**source_map_getter,
|
||||
);
|
||||
source_line_frame_index = Some(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if let Some(frame) = frames.first() {
|
||||
if let Some(file_name) = &frame.file_name {
|
||||
if !file_name.trim_start_matches('[').starts_with("ext:") {
|
||||
source_line = msg
|
||||
.get_source_line(scope)
|
||||
.map(|v| v.to_rust_string_lossy(scope));
|
||||
source_line_frame_index = Some(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut aggregated: Option<Vec<JsError>> = None;
|
||||
if is_aggregate_error(scope, v8_exception) {
|
||||
// Read an array of stored errors, this is only defined for `AggregateError`
|
||||
let aggregated_errors = get_property(scope, exception, "errors");
|
||||
let aggregated_errors: Option<v8::Local<v8::Array>> =
|
||||
aggregated_errors.and_then(|a| a.try_into().ok());
|
||||
|
||||
if let Some(errors) = aggregated_errors {
|
||||
if errors.length() > 0 {
|
||||
let mut agg = vec![];
|
||||
for i in 0..errors.length() {
|
||||
let error = errors.get_index(scope, i).unwrap();
|
||||
let js_error = Self::from_v8_exception(scope, error);
|
||||
agg.push(js_error);
|
||||
}
|
||||
aggregated = Some(agg);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Self {
|
||||
name: e.name,
|
||||
message: e.message,
|
||||
exception_message,
|
||||
cause,
|
||||
source_line,
|
||||
source_line_frame_index,
|
||||
frames,
|
||||
stack,
|
||||
aggregated,
|
||||
}
|
||||
} else {
|
||||
let exception_message = exception_message
|
||||
.unwrap_or_else(|| msg.get(scope).to_rust_string_lossy(scope));
|
||||
// The exception is not a JS Error object.
|
||||
// Get the message given by V8::Exception::create_message(), and provide
|
||||
// empty frames.
|
||||
Self {
|
||||
name: None,
|
||||
message: None,
|
||||
exception_message,
|
||||
cause: None,
|
||||
source_line: None,
|
||||
source_line_frame_index: None,
|
||||
frames: vec![],
|
||||
stack: None,
|
||||
aggregated: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for JsError {}
|
||||
|
||||
impl Display for JsError {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
if let Some(stack) = &self.stack {
|
||||
let stack_lines = stack.lines();
|
||||
if stack_lines.count() > 1 {
|
||||
return write!(f, "{stack}");
|
||||
}
|
||||
}
|
||||
write!(f, "{}", self.exception_message)?;
|
||||
let location = self.frames.first().and_then(|f| f.maybe_format_location());
|
||||
if let Some(location) = location {
|
||||
write!(f, "\n at {location}")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(piscisaureus): rusty_v8 should implement the Error trait on
|
||||
// values of type v8::Global<T>.
|
||||
pub(crate) fn to_v8_type_error(
|
||||
scope: &mut v8::HandleScope,
|
||||
err: Error,
|
||||
) -> v8::Global<v8::Value> {
|
||||
let err_string = err.to_string();
|
||||
let error_chain = err
|
||||
.chain()
|
||||
.skip(1)
|
||||
.filter(|e| e.to_string() != err_string)
|
||||
.map(|e| e.to_string())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let message = if !error_chain.is_empty() {
|
||||
format!(
|
||||
"{}\n Caused by:\n {}",
|
||||
err_string,
|
||||
error_chain.join("\n ")
|
||||
)
|
||||
} else {
|
||||
err_string
|
||||
};
|
||||
|
||||
let message = v8::String::new(scope, &message).unwrap();
|
||||
let exception = v8::Exception::type_error(scope, message);
|
||||
v8::Global::new(scope, exception)
|
||||
}
|
||||
|
||||
/// Implements `value instanceof primordials.Error` in JS. Similar to
|
||||
/// `Value::is_native_error()` but more closely matches the semantics
|
||||
/// of `instanceof`. `Value::is_native_error()` also checks for static class
|
||||
/// inheritance rather than just scanning the prototype chain, which doesn't
|
||||
/// work with our WebIDL implementation of `DOMException`.
|
||||
pub(crate) fn is_instance_of_error(
|
||||
scope: &mut v8::HandleScope,
|
||||
value: v8::Local<v8::Value>,
|
||||
) -> bool {
|
||||
if !value.is_object() {
|
||||
return false;
|
||||
}
|
||||
let message = v8::String::empty(scope);
|
||||
let error_prototype = v8::Exception::error(scope, message)
|
||||
.to_object(scope)
|
||||
.unwrap()
|
||||
.get_prototype(scope)
|
||||
.unwrap();
|
||||
let mut maybe_prototype =
|
||||
value.to_object(scope).unwrap().get_prototype(scope);
|
||||
while let Some(prototype) = maybe_prototype {
|
||||
if !prototype.is_object() {
|
||||
return false;
|
||||
}
|
||||
if prototype.strict_equals(error_prototype) {
|
||||
return true;
|
||||
}
|
||||
maybe_prototype = prototype
|
||||
.to_object(scope)
|
||||
.and_then(|o| o.get_prototype(scope));
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// Implements `value instanceof primordials.AggregateError` in JS,
|
||||
/// by walking the prototype chain, and comparing each links constructor `name` property.
|
||||
///
|
||||
/// NOTE: There is currently no way to detect `AggregateError` via `rusty_v8`,
|
||||
/// as v8 itself doesn't expose `v8__Exception__AggregateError`,
|
||||
/// and we cannot create bindings for it. This forces us to rely on `name` inference.
|
||||
pub(crate) fn is_aggregate_error(
|
||||
scope: &mut v8::HandleScope,
|
||||
value: v8::Local<v8::Value>,
|
||||
) -> bool {
|
||||
let mut maybe_prototype = Some(value);
|
||||
while let Some(prototype) = maybe_prototype {
|
||||
if !prototype.is_object() {
|
||||
return false;
|
||||
}
|
||||
|
||||
let prototype = prototype.to_object(scope).unwrap();
|
||||
let prototype_name = match get_property(scope, prototype, "constructor") {
|
||||
Some(constructor) => {
|
||||
let ctor = constructor.to_object(scope).unwrap();
|
||||
get_property(scope, ctor, "name").map(|v| v.to_rust_string_lossy(scope))
|
||||
}
|
||||
None => return false,
|
||||
};
|
||||
|
||||
if prototype_name == Some(String::from("AggregateError")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
maybe_prototype = prototype.get_prototype(scope);
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
const DATA_URL_ABBREV_THRESHOLD: usize = 150;
|
||||
|
||||
pub fn format_file_name(file_name: &str) -> String {
|
||||
abbrev_file_name(file_name).unwrap_or_else(|| file_name.to_string())
|
||||
}
|
||||
|
||||
fn abbrev_file_name(file_name: &str) -> Option<String> {
|
||||
if file_name.len() <= DATA_URL_ABBREV_THRESHOLD {
|
||||
return None;
|
||||
}
|
||||
let url = Url::parse(file_name).ok()?;
|
||||
if url.scheme() != "data" {
|
||||
return None;
|
||||
}
|
||||
let (head, tail) = url.path().split_once(',')?;
|
||||
let len = tail.len();
|
||||
let start = tail.get(0..20)?;
|
||||
let end = tail.get(len - 20..)?;
|
||||
Some(format!("{}:{},{}......{}", url.scheme(), head, start, end))
|
||||
}
|
||||
|
||||
pub(crate) fn exception_to_err_result<T>(
|
||||
scope: &mut v8::HandleScope,
|
||||
exception: v8::Local<v8::Value>,
|
||||
in_promise: bool,
|
||||
) -> Result<T, Error> {
|
||||
let state_rc = JsRuntime::state_from(scope);
|
||||
|
||||
let was_terminating_execution = scope.is_execution_terminating();
|
||||
// Disable running microtasks for a moment. When upgrading to V8 v11.4
|
||||
// we discovered that canceling termination here will cause the queued
|
||||
// microtasks to run which breaks some tests.
|
||||
scope.set_microtasks_policy(v8::MicrotasksPolicy::Explicit);
|
||||
// If TerminateExecution was called, cancel isolate termination so that the
|
||||
// exception can be created. Note that `scope.is_execution_terminating()` may
|
||||
// have returned false if TerminateExecution was indeed called but there was
|
||||
// no JS to execute after the call.
|
||||
scope.cancel_terminate_execution();
|
||||
let mut exception = exception;
|
||||
{
|
||||
// If termination is the result of a `op_dispatch_exception` call, we want
|
||||
// to use the exception that was passed to it rather than the exception that
|
||||
// was passed to this function.
|
||||
let state = state_rc.borrow();
|
||||
exception = if let Some(exception) = &state.dispatched_exception {
|
||||
v8::Local::new(scope, exception.clone())
|
||||
} else if was_terminating_execution && exception.is_null_or_undefined() {
|
||||
let message = v8::String::new(scope, "execution terminated").unwrap();
|
||||
v8::Exception::error(scope, message)
|
||||
} else {
|
||||
exception
|
||||
};
|
||||
}
|
||||
|
||||
let mut js_error = JsError::from_v8_exception(scope, exception);
|
||||
if in_promise {
|
||||
js_error.exception_message = format!(
|
||||
"Uncaught (in promise) {}",
|
||||
js_error.exception_message.trim_start_matches("Uncaught ")
|
||||
);
|
||||
}
|
||||
|
||||
if was_terminating_execution {
|
||||
// Resume exception termination.
|
||||
scope.terminate_execution();
|
||||
}
|
||||
scope.set_microtasks_policy(v8::MicrotasksPolicy::Auto);
|
||||
|
||||
Err(js_error.into())
|
||||
}
|
||||
|
||||
pub fn throw_type_error(scope: &mut v8::HandleScope, message: impl AsRef<str>) {
|
||||
let message = v8::String::new(scope, message.as_ref()).unwrap();
|
||||
let exception = v8::Exception::type_error(scope, message);
|
||||
scope.throw_exception(exception);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_bad_resource() {
|
||||
let err = bad_resource("Resource has been closed");
|
||||
assert_eq!(err.to_string(), "Resource has been closed");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bad_resource_id() {
|
||||
let err = bad_resource_id();
|
||||
assert_eq!(err.to_string(), "Bad resource ID");
|
||||
}
|
||||
}
|
|
@ -1,216 +0,0 @@
|
|||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
||||
|
||||
use anyhow::Error;
|
||||
|
||||
pub fn get_error_code(err: &Error) -> Option<&'static str> {
|
||||
err
|
||||
.downcast_ref::<std::io::Error>()
|
||||
.map(|e| match e.raw_os_error() {
|
||||
Some(code) => get_os_error_code(code),
|
||||
None => get_io_error_code(e),
|
||||
})
|
||||
.and_then(|code| match code.is_empty() {
|
||||
true => None,
|
||||
false => Some(code),
|
||||
})
|
||||
}
|
||||
|
||||
fn get_io_error_code(err: &std::io::Error) -> &'static str {
|
||||
// not exhaustive but simple and possibly sufficient once `io_error_more` is stabilized (https://github.com/rust-lang/rust/issues/86442)
|
||||
// inversion of https://github.com/rust-lang/rust/blob/dca3f1b786efd27be3b325ed1e01e247aa589c3b/library/std/src/sys/unix/mod.rs#L138-L185
|
||||
// TODO(@AaronO): revisit as `io_error_more` lands in rust stable
|
||||
use std::io::ErrorKind;
|
||||
match err.kind() {
|
||||
// ErrorKind::ArgumentListTooLong => "E2BIG",
|
||||
ErrorKind::AddrInUse => "EADDRINUSE",
|
||||
ErrorKind::AddrNotAvailable => "EADDRNOTAVAIL",
|
||||
// ErrorKind::ResourceBusy => "EBUSY",
|
||||
ErrorKind::ConnectionAborted => "ECONNABORTED",
|
||||
ErrorKind::ConnectionRefused => "ECONNREFUSED",
|
||||
ErrorKind::ConnectionReset => "ECONNRESET",
|
||||
// ErrorKind::Deadlock => "EDEADLK",
|
||||
// ErrorKind::FilesystemQuotaExceeded => "EDQUOT",
|
||||
ErrorKind::AlreadyExists => "EEXIST",
|
||||
// ErrorKind::FileTooLarge => "EFBIG",
|
||||
// ErrorKind::HostUnreachable => "EHOSTUNREACH",
|
||||
ErrorKind::Interrupted => "EINTR",
|
||||
ErrorKind::InvalidInput => "EINVAL",
|
||||
// ErrorKind::IsADirectory => "EISDIR",
|
||||
// ErrorKind::FilesystemLoop => "ELOOP",
|
||||
ErrorKind::NotFound => "ENOENT",
|
||||
ErrorKind::OutOfMemory => "ENOMEM",
|
||||
// ErrorKind::StorageFull => "ENOSPC",
|
||||
ErrorKind::Unsupported => "ENOSYS",
|
||||
// ErrorKind::TooManyLinks => "EMLINK",
|
||||
// ErrorKind::FilenameTooLong => "ENAMETOOLONG",
|
||||
// ErrorKind::NetworkDown => "ENETDOWN",
|
||||
// ErrorKind::NetworkUnreachable => "ENETUNREACH",
|
||||
ErrorKind::NotConnected => "ENOTCONN",
|
||||
// ErrorKind::NotADirectory => "ENOTDIR",
|
||||
// ErrorKind::DirectoryNotEmpty => "ENOTEMPTY",
|
||||
ErrorKind::BrokenPipe => "EPIPE",
|
||||
// ErrorKind::ReadOnlyFilesystem => "EROFS",
|
||||
// ErrorKind::NotSeekable => "ESPIPE",
|
||||
// ErrorKind::StaleNetworkFileHandle => "ESTALE",
|
||||
ErrorKind::TimedOut => "ETIMEDOUT",
|
||||
// ErrorKind::ExecutableFileBusy => "ETXTBSY",
|
||||
// ErrorKind::CrossesDevices => "EXDEV",
|
||||
ErrorKind::PermissionDenied => "EACCES", // NOTE: Collides with EPERM ...
|
||||
ErrorKind::WouldBlock => "EWOULDBLOCK", // NOTE: Collides with EAGAIN ...
|
||||
_ => "",
|
||||
}
|
||||
}
|
||||
|
||||
/// Maps OS errno codes to string names
|
||||
/// derived from libuv: https://github.com/libuv/libuv/blob/26b2e5dbb6301756644d6e4cf6ca9c49c00513d3/include/uv/errno.h
|
||||
/// generated with tools/codegen_error_codes.js
|
||||
#[cfg(unix)]
|
||||
fn get_os_error_code(errno: i32) -> &'static str {
|
||||
match errno {
|
||||
libc::E2BIG => "E2BIG",
|
||||
libc::EACCES => "EACCES",
|
||||
libc::EADDRINUSE => "EADDRINUSE",
|
||||
libc::EADDRNOTAVAIL => "EADDRNOTAVAIL",
|
||||
libc::EAFNOSUPPORT => "EAFNOSUPPORT",
|
||||
libc::EAGAIN => "EAGAIN",
|
||||
libc::EALREADY => "EALREADY",
|
||||
libc::EBADF => "EBADF",
|
||||
libc::EBUSY => "EBUSY",
|
||||
libc::ECANCELED => "ECANCELED",
|
||||
libc::ECONNABORTED => "ECONNABORTED",
|
||||
libc::ECONNREFUSED => "ECONNREFUSED",
|
||||
libc::ECONNRESET => "ECONNRESET",
|
||||
libc::EEXIST => "EEXIST",
|
||||
libc::EFAULT => "EFAULT",
|
||||
libc::EHOSTUNREACH => "EHOSTUNREACH",
|
||||
libc::EINVAL => "EINVAL",
|
||||
libc::EIO => "EIO",
|
||||
libc::EISCONN => "EISCONN",
|
||||
libc::EISDIR => "EISDIR",
|
||||
libc::ELOOP => "ELOOP",
|
||||
libc::EMFILE => "EMFILE",
|
||||
libc::EMSGSIZE => "EMSGSIZE",
|
||||
libc::ENAMETOOLONG => "ENAMETOOLONG",
|
||||
libc::ENETUNREACH => "ENETUNREACH",
|
||||
libc::ENOBUFS => "ENOBUFS",
|
||||
libc::ENOENT => "ENOENT",
|
||||
libc::ENOMEM => "ENOMEM",
|
||||
libc::ENOSPC => "ENOSPC",
|
||||
libc::ENOTCONN => "ENOTCONN",
|
||||
libc::ENOTDIR => "ENOTDIR",
|
||||
libc::ENOTEMPTY => "ENOTEMPTY",
|
||||
libc::ENOTSOCK => "ENOTSOCK",
|
||||
libc::ENOTSUP => "ENOTSUP",
|
||||
libc::EPERM => "EPERM",
|
||||
libc::EPIPE => "EPIPE",
|
||||
libc::EPROTONOSUPPORT => "EPROTONOSUPPORT",
|
||||
libc::EROFS => "EROFS",
|
||||
libc::ETIMEDOUT => "ETIMEDOUT",
|
||||
libc::EXDEV => "EXDEV",
|
||||
libc::ESOCKTNOSUPPORT => "ESOCKTNOSUPPORT",
|
||||
_ => "",
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn get_os_error_code(errno: i32) -> &'static str {
|
||||
match errno {
|
||||
998 => "EACCES", // ERROR_NOACCESS
|
||||
10013 => "EACCES", // WSAEACCES
|
||||
1920 => "EACCES", // ERROR_CANT_ACCESS_FILE
|
||||
1227 => "EADDRINUSE", // ERROR_ADDRESS_ALREADY_ASSOCIATED
|
||||
10048 => "EADDRINUSE", // WSAEADDRINUSE
|
||||
10049 => "EADDRNOTAVAIL", // WSAEADDRNOTAVAIL
|
||||
10047 => "EAFNOSUPPORT", // WSAEAFNOSUPPORT
|
||||
10035 => "EAGAIN", // WSAEWOULDBLOCK
|
||||
10037 => "EALREADY", // WSAEALREADY
|
||||
1004 => "EBADF", // ERROR_INVALID_FLAGS
|
||||
6 => "EBADF", // ERROR_INVALID_HANDLE
|
||||
33 => "EBUSY", // ERROR_LOCK_VIOLATION
|
||||
231 => "EBUSY", // ERROR_PIPE_BUSY
|
||||
32 => "EBUSY", // ERROR_SHARING_VIOLATION
|
||||
995 => "ECANCELED", // ERROR_OPERATION_ABORTED
|
||||
10004 => "ECANCELED", // WSAEINTR
|
||||
1236 => "ECONNABORTED", // ERROR_CONNECTION_ABORTED
|
||||
10053 => "ECONNABORTED", // WSAECONNABORTED
|
||||
1225 => "ECONNREFUSED", // ERROR_CONNECTION_REFUSED
|
||||
10061 => "ECONNREFUSED", // WSAECONNREFUSED
|
||||
64 => "ECONNRESET", // ERROR_NETNAME_DELETED
|
||||
10054 => "ECONNRESET", // WSAECONNRESET
|
||||
183 => "EEXIST", // ERROR_ALREADY_EXISTS
|
||||
80 => "EEXIST", // ERROR_FILE_EXISTS
|
||||
111 => "EFAULT", // ERROR_BUFFER_OVERFLOW
|
||||
10014 => "EFAULT", // WSAEFAULT
|
||||
1232 => "EHOSTUNREACH", // ERROR_HOST_UNREACHABLE
|
||||
10065 => "EHOSTUNREACH", // WSAEHOSTUNREACH
|
||||
122 => "EINVAL", // ERROR_INSUFFICIENT_BUFFER
|
||||
13 => "EINVAL", // ERROR_INVALID_DATA
|
||||
87 => "EINVAL", // ERROR_INVALID_PARAMETER
|
||||
1464 => "EINVAL", // ERROR_SYMLINK_NOT_SUPPORTED
|
||||
10022 => "EINVAL", // WSAEINVAL
|
||||
10046 => "EINVAL", // WSAEPFNOSUPPORT
|
||||
1102 => "EIO", // ERROR_BEGINNING_OF_MEDIA
|
||||
1111 => "EIO", // ERROR_BUS_RESET
|
||||
23 => "EIO", // ERROR_CRC
|
||||
1166 => "EIO", // ERROR_DEVICE_DOOR_OPEN
|
||||
1165 => "EIO", // ERROR_DEVICE_REQUIRES_CLEANING
|
||||
1393 => "EIO", // ERROR_DISK_CORRUPT
|
||||
1129 => "EIO", // ERROR_EOM_OVERFLOW
|
||||
1101 => "EIO", // ERROR_FILEMARK_DETECTED
|
||||
31 => "EIO", // ERROR_GEN_FAILURE
|
||||
1106 => "EIO", // ERROR_INVALID_BLOCK_LENGTH
|
||||
1117 => "EIO", // ERROR_IO_DEVICE
|
||||
1104 => "EIO", // ERROR_NO_DATA_DETECTED
|
||||
205 => "EIO", // ERROR_NO_SIGNAL_SENT
|
||||
110 => "EIO", // ERROR_OPEN_FAILED
|
||||
1103 => "EIO", // ERROR_SETMARK_DETECTED
|
||||
156 => "EIO", // ERROR_SIGNAL_REFUSED
|
||||
10056 => "EISCONN", // WSAEISCONN
|
||||
1921 => "ELOOP", // ERROR_CANT_RESOLVE_FILENAME
|
||||
4 => "EMFILE", // ERROR_TOO_MANY_OPEN_FILES
|
||||
10024 => "EMFILE", // WSAEMFILE
|
||||
10040 => "EMSGSIZE", // WSAEMSGSIZE
|
||||
206 => "ENAMETOOLONG", // ERROR_FILENAME_EXCED_RANGE
|
||||
1231 => "ENETUNREACH", // ERROR_NETWORK_UNREACHABLE
|
||||
10051 => "ENETUNREACH", // WSAENETUNREACH
|
||||
10055 => "ENOBUFS", // WSAENOBUFS
|
||||
161 => "ENOENT", // ERROR_BAD_PATHNAME
|
||||
267 => "ENOENT", // ERROR_DIRECTORY
|
||||
203 => "ENOENT", // ERROR_ENVVAR_NOT_FOUND
|
||||
2 => "ENOENT", // ERROR_FILE_NOT_FOUND
|
||||
123 => "ENOENT", // ERROR_INVALID_NAME
|
||||
15 => "ENOENT", // ERROR_INVALID_DRIVE
|
||||
4392 => "ENOENT", // ERROR_INVALID_REPARSE_DATA
|
||||
126 => "ENOENT", // ERROR_MOD_NOT_FOUND
|
||||
3 => "ENOENT", // ERROR_PATH_NOT_FOUND
|
||||
11001 => "ENOENT", // WSAHOST_NOT_FOUND
|
||||
11004 => "ENOENT", // WSANO_DATA
|
||||
8 => "ENOMEM", // ERROR_NOT_ENOUGH_MEMORY
|
||||
14 => "ENOMEM", // ERROR_OUTOFMEMORY
|
||||
82 => "ENOSPC", // ERROR_CANNOT_MAKE
|
||||
112 => "ENOSPC", // ERROR_DISK_FULL
|
||||
277 => "ENOSPC", // ERROR_EA_TABLE_FULL
|
||||
1100 => "ENOSPC", // ERROR_END_OF_MEDIA
|
||||
39 => "ENOSPC", // ERROR_HANDLE_DISK_FULL
|
||||
2250 => "ENOTCONN", // ERROR_NOT_CONNECTED
|
||||
10057 => "ENOTCONN", // WSAENOTCONN
|
||||
145 => "ENOTEMPTY", // ERROR_DIR_NOT_EMPTY
|
||||
10038 => "ENOTSOCK", // WSAENOTSOCK
|
||||
50 => "ENOTSUP", // ERROR_NOT_SUPPORTED
|
||||
5 => "EPERM", // ERROR_ACCESS_DENIED
|
||||
1314 => "EPERM", // ERROR_PRIVILEGE_NOT_HELD
|
||||
230 => "EPIPE", // ERROR_BAD_PIPE
|
||||
232 => "EPIPE", // ERROR_NO_DATA
|
||||
233 => "EPIPE", // ERROR_PIPE_NOT_CONNECTED
|
||||
10058 => "EPIPE", // WSAESHUTDOWN
|
||||
10043 => "EPROTONOSUPPORT", // WSAEPROTONOSUPPORT
|
||||
19 => "EROFS", // ERROR_WRITE_PROTECT
|
||||
121 => "ETIMEDOUT", // ERROR_SEM_TIMEOUT
|
||||
10060 => "ETIMEDOUT", // WSAETIMEDOUT
|
||||
17 => "EXDEV", // ERROR_NOT_SAME_DEVICE
|
||||
1 => "EISDIR", // ERROR_INVALID_FUNCTION
|
||||
208 => "E2BIG", // ERROR_META_EXPANSION_TOO_LONG
|
||||
10044 => "ESOCKTNOSUPPORT", // WSAESOCKTNOSUPPORT
|
||||
_ => "",
|
||||
}
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
||||
//! This example shows you how to define ops in Rust and then call them from
|
||||
//! JavaScript.
|
||||
|
||||
use deno_core::Extension;
|
||||
use deno_core::JsRuntime;
|
||||
use deno_core::RuntimeOptions;
|
||||
|
||||
fn main() {
|
||||
let my_ext = Extension::builder("my_ext")
|
||||
.middleware(|op| match op.name {
|
||||
"op_print" => op.disable(),
|
||||
_ => op,
|
||||
})
|
||||
.build();
|
||||
|
||||
// Initialize a runtime instance
|
||||
let mut runtime = JsRuntime::new(RuntimeOptions {
|
||||
extensions: vec![my_ext],
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
// Deno.core.print() will now be a NOP
|
||||
runtime
|
||||
.execute_script_static("<usage>", r#"Deno.core.print("I'm broken")"#)
|
||||
.unwrap();
|
||||
}
|
|
@ -1,48 +0,0 @@
|
|||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
||||
//! This example shows you how to evaluate JavaScript expression and deserialize
|
||||
//! return value into a Rust object.
|
||||
|
||||
// NOTE:
|
||||
// Here we are deserializing to `serde_json::Value` but you can
|
||||
// deserialize to any other type that implements the `Deserialize` trait.
|
||||
|
||||
use deno_core::v8;
|
||||
use deno_core::JsRuntime;
|
||||
use deno_core::RuntimeOptions;
|
||||
|
||||
fn main() {
|
||||
let mut runtime = JsRuntime::new(RuntimeOptions::default());
|
||||
|
||||
// Evaluate some code
|
||||
let code = "let a = 1+4; a*2";
|
||||
let output: serde_json::Value =
|
||||
eval(&mut runtime, code).expect("Eval failed");
|
||||
|
||||
println!("Output: {output:?}");
|
||||
|
||||
let expected_output = serde_json::json!(10);
|
||||
assert_eq!(expected_output, output);
|
||||
}
|
||||
|
||||
fn eval(
|
||||
context: &mut JsRuntime,
|
||||
code: &'static str,
|
||||
) -> Result<serde_json::Value, String> {
|
||||
let res = context.execute_script_static("<anon>", code);
|
||||
match res {
|
||||
Ok(global) => {
|
||||
let scope = &mut context.handle_scope();
|
||||
let local = v8::Local::new(scope, global);
|
||||
// Deserialize a `v8` object into a Rust type using `serde_v8`,
|
||||
// in this case deserialize to a JSON `Value`.
|
||||
let deserialized_value =
|
||||
serde_v8::from_v8::<serde_json::Value>(scope, local);
|
||||
|
||||
match deserialized_value {
|
||||
Ok(value) => Ok(value),
|
||||
Err(err) => Err(format!("Cannot deserialize value: {err:?}")),
|
||||
}
|
||||
}
|
||||
Err(err) => Err(format!("Evaling error: {err:?}")),
|
||||
}
|
||||
}
|
|
@ -1,40 +0,0 @@
|
|||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
||||
|
||||
use anyhow::Context;
|
||||
use deno_core::anyhow::Error;
|
||||
use deno_core::FsModuleLoader;
|
||||
use deno_core::JsRuntime;
|
||||
use deno_core::RuntimeOptions;
|
||||
use std::rc::Rc;
|
||||
|
||||
fn main() -> Result<(), Error> {
|
||||
let args: Vec<String> = std::env::args().collect();
|
||||
if args.len() < 2 {
|
||||
println!("Usage: target/examples/debug/fs_module_loader <path_to_module>");
|
||||
std::process::exit(1);
|
||||
}
|
||||
let main_url = &args[1];
|
||||
println!("Run {main_url}");
|
||||
|
||||
let mut js_runtime = JsRuntime::new(RuntimeOptions {
|
||||
module_loader: Some(Rc::new(FsModuleLoader)),
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let runtime = tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()?;
|
||||
|
||||
let main_module = deno_core::resolve_path(
|
||||
main_url,
|
||||
&std::env::current_dir().context("Unable to get CWD")?,
|
||||
)?;
|
||||
|
||||
let future = async move {
|
||||
let mod_id = js_runtime.load_main_module(&main_module, None).await?;
|
||||
let result = js_runtime.mod_evaluate(mod_id);
|
||||
js_runtime.run_event_loop(false).await?;
|
||||
result.await?
|
||||
};
|
||||
runtime.block_on(future)
|
||||
}
|
|
@ -1,68 +0,0 @@
|
|||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
||||
//! This example shows you how to define ops in Rust and then call them from
|
||||
//! JavaScript.
|
||||
|
||||
use deno_core::op;
|
||||
use deno_core::Extension;
|
||||
use deno_core::JsRuntime;
|
||||
use deno_core::RuntimeOptions;
|
||||
|
||||
// This is a hack to make the `#[op]` macro work with
|
||||
// deno_core examples.
|
||||
// You can remove this:
|
||||
use deno_core::*;
|
||||
|
||||
#[op]
|
||||
fn op_sum(nums: Vec<f64>) -> Result<f64, deno_core::error::AnyError> {
|
||||
// Sum inputs
|
||||
let sum = nums.iter().fold(0.0, |a, v| a + v);
|
||||
// return as a Result<f64, AnyError>
|
||||
Ok(sum)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// Build a deno_core::Extension providing custom ops
|
||||
let ext = Extension::builder("my_ext")
|
||||
.ops(vec![
|
||||
// An op for summing an array of numbers
|
||||
// The op-layer automatically deserializes inputs
|
||||
// and serializes the returned Result & value
|
||||
op_sum::decl(),
|
||||
])
|
||||
.build();
|
||||
|
||||
// Initialize a runtime instance
|
||||
let mut runtime = JsRuntime::new(RuntimeOptions {
|
||||
extensions: vec![ext],
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
// Now we see how to invoke the op we just defined. The runtime automatically
|
||||
// contains a Deno.core object with several functions for interacting with it.
|
||||
// You can find its definition in core.js.
|
||||
runtime
|
||||
.execute_script_static(
|
||||
"<usage>",
|
||||
r#"
|
||||
// Print helper function, calling Deno.core.print()
|
||||
function print(value) {
|
||||
Deno.core.print(value.toString()+"\n");
|
||||
}
|
||||
|
||||
const arr = [1, 2, 3];
|
||||
print("The sum of");
|
||||
print(arr);
|
||||
print("is");
|
||||
print(Deno.core.ops.op_sum(arr));
|
||||
|
||||
// And incorrect usage
|
||||
try {
|
||||
print(Deno.core.ops.op_sum(0));
|
||||
} catch(e) {
|
||||
print('Exception:');
|
||||
print(e);
|
||||
}
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
|
@ -1,46 +0,0 @@
|
|||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
||||
// This is not a real HTTP server. We read blindly one time into 'requestBuf',
|
||||
// then write this fixed 'responseBuf'. The point of this benchmark is to
|
||||
// exercise the event loop in a simple yet semi-realistic way.
|
||||
|
||||
// deno-lint-ignore-file camelcase
|
||||
|
||||
const { op_listen } = Deno.core.ops;
|
||||
const {
|
||||
op_accept,
|
||||
op_read_socket,
|
||||
} = Deno.core.ensureFastOps();
|
||||
|
||||
const requestBuf = new Uint8Array(64 * 1024);
|
||||
const responseBuf = new Uint8Array(
|
||||
"HTTP/1.1 200 OK\r\nContent-Length: 12\r\n\r\nHello World\n"
|
||||
.split("")
|
||||
.map((c) => c.charCodeAt(0)),
|
||||
);
|
||||
|
||||
async function serve(rid) {
|
||||
try {
|
||||
while (true) {
|
||||
await op_read_socket(rid, requestBuf);
|
||||
if (!ops.op_try_write(rid, responseBuf)) {
|
||||
await Deno.core.writeAll(rid, responseBuf);
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// pass
|
||||
}
|
||||
Deno.core.close(rid);
|
||||
}
|
||||
|
||||
async function main() {
|
||||
/** Listens on 0.0.0.0:4570, returns rid. */
|
||||
const listenerRid = op_listen();
|
||||
Deno.core.print(`http_bench_ops listening on http://127.0.0.1:4570/\n`);
|
||||
|
||||
while (true) {
|
||||
const rid = await op_accept(listenerRid);
|
||||
serve(rid);
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
|
@ -1,176 +0,0 @@
|
|||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
||||
use deno_core::anyhow::Error;
|
||||
use deno_core::op;
|
||||
use deno_core::AsyncRefCell;
|
||||
use deno_core::AsyncResult;
|
||||
use deno_core::JsBuffer;
|
||||
use deno_core::JsRuntimeForSnapshot;
|
||||
use deno_core::OpState;
|
||||
use deno_core::Resource;
|
||||
use deno_core::ResourceId;
|
||||
use std::cell::RefCell;
|
||||
use std::env;
|
||||
use std::net::SocketAddr;
|
||||
use std::rc::Rc;
|
||||
use tokio::io::AsyncReadExt;
|
||||
use tokio::io::AsyncWriteExt;
|
||||
|
||||
// This is a hack to make the `#[op]` macro work with
|
||||
// deno_core examples.
|
||||
// You can remove this:
|
||||
use deno_core::*;
|
||||
|
||||
// Note: a `tokio::net::TcpListener` doesn't need to be wrapped in a cell,
|
||||
// because it only supports one op (`accept`) which does not require a mutable
|
||||
// reference to the listener.
|
||||
struct TcpListener {
|
||||
inner: tokio::net::TcpListener,
|
||||
}
|
||||
|
||||
impl TcpListener {
|
||||
async fn accept(self: Rc<Self>) -> Result<TcpStream, std::io::Error> {
|
||||
let stream = self.inner.accept().await?.0.into();
|
||||
Ok(stream)
|
||||
}
|
||||
}
|
||||
|
||||
impl Resource for TcpListener {
|
||||
fn close(self: Rc<Self>) {}
|
||||
}
|
||||
|
||||
impl TryFrom<std::net::TcpListener> for TcpListener {
|
||||
type Error = std::io::Error;
|
||||
fn try_from(
|
||||
std_listener: std::net::TcpListener,
|
||||
) -> Result<Self, Self::Error> {
|
||||
tokio::net::TcpListener::try_from(std_listener).map(|tokio_listener| Self {
|
||||
inner: tokio_listener,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
struct TcpStream {
|
||||
rd: AsyncRefCell<tokio::net::tcp::OwnedReadHalf>,
|
||||
wr: AsyncRefCell<tokio::net::tcp::OwnedWriteHalf>,
|
||||
}
|
||||
|
||||
impl TcpStream {
|
||||
async fn read(self: Rc<Self>, data: &mut [u8]) -> Result<usize, Error> {
|
||||
let mut rd = RcRef::map(&self, |r| &r.rd).borrow_mut().await;
|
||||
let nread = rd.read(data).await?;
|
||||
Ok(nread)
|
||||
}
|
||||
|
||||
async fn write(self: Rc<Self>, data: &[u8]) -> Result<usize, Error> {
|
||||
let mut wr = RcRef::map(self, |r| &r.wr).borrow_mut().await;
|
||||
let nwritten = wr.write(data).await?;
|
||||
Ok(nwritten)
|
||||
}
|
||||
|
||||
fn try_write(self: Rc<Self>, data: &[u8]) -> Result<usize, Error> {
|
||||
let wr = RcRef::map(self, |r| &r.wr)
|
||||
.try_borrow_mut()
|
||||
.ok_or_else(|| Error::msg("Failed to acquire lock on TcpStream"))?;
|
||||
let nwritten = wr.try_write(data)?;
|
||||
Ok(nwritten)
|
||||
}
|
||||
}
|
||||
|
||||
impl Resource for TcpStream {
|
||||
deno_core::impl_readable_byob!();
|
||||
deno_core::impl_writable!();
|
||||
|
||||
fn close(self: Rc<Self>) {}
|
||||
}
|
||||
|
||||
impl From<tokio::net::TcpStream> for TcpStream {
|
||||
fn from(s: tokio::net::TcpStream) -> Self {
|
||||
let (rd, wr) = s.into_split();
|
||||
Self {
|
||||
rd: rd.into(),
|
||||
wr: wr.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn create_js_runtime() -> JsRuntimeForSnapshot {
|
||||
let ext = deno_core::Extension::builder("my_ext")
|
||||
.ops(vec![
|
||||
op_listen::decl(),
|
||||
op_accept::decl(),
|
||||
op_try_write::decl(),
|
||||
op_read_socket::decl(),
|
||||
])
|
||||
.build();
|
||||
|
||||
JsRuntimeForSnapshot::new(
|
||||
deno_core::RuntimeOptions {
|
||||
extensions: vec![ext],
|
||||
..Default::default()
|
||||
},
|
||||
Default::default(),
|
||||
)
|
||||
}
|
||||
|
||||
#[op]
|
||||
async fn op_read_socket(
|
||||
state: Rc<RefCell<OpState>>,
|
||||
rid: ResourceId,
|
||||
mut data: JsBuffer,
|
||||
) -> Result<u32, Error> {
|
||||
let resource = state.borrow_mut().resource_table.get::<TcpStream>(rid)?;
|
||||
let nread = resource.read(&mut data).await?;
|
||||
Ok(nread as u32)
|
||||
}
|
||||
|
||||
#[op]
|
||||
fn op_listen(state: &mut OpState) -> Result<ResourceId, Error> {
|
||||
let addr = "127.0.0.1:4570".parse::<SocketAddr>().unwrap();
|
||||
let std_listener = std::net::TcpListener::bind(addr)?;
|
||||
std_listener.set_nonblocking(true)?;
|
||||
let listener = TcpListener::try_from(std_listener)?;
|
||||
let rid = state.resource_table.add(listener);
|
||||
Ok(rid)
|
||||
}
|
||||
|
||||
#[op]
|
||||
async fn op_accept(
|
||||
state: Rc<RefCell<OpState>>,
|
||||
rid: ResourceId,
|
||||
) -> Result<ResourceId, Error> {
|
||||
let listener = state.borrow().resource_table.get::<TcpListener>(rid)?;
|
||||
let stream = listener.accept().await?;
|
||||
let rid = state.borrow_mut().resource_table.add(stream);
|
||||
Ok(rid)
|
||||
}
|
||||
|
||||
#[op(fast)]
|
||||
fn op_try_write(
|
||||
state: &mut OpState,
|
||||
rid: u32,
|
||||
value: &[u8],
|
||||
) -> Result<bool, Error> {
|
||||
let stream = state.resource_table.get::<TcpStream>(rid)?;
|
||||
Ok(stream.try_write(value).is_ok())
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// NOTE: `--help` arg will display V8 help and exit
|
||||
deno_core::v8_set_flags(env::args().collect());
|
||||
|
||||
let mut js_runtime = create_js_runtime();
|
||||
let runtime = tokio::runtime::Builder::new_current_thread()
|
||||
.enable_io()
|
||||
.build()
|
||||
.unwrap();
|
||||
let future = async move {
|
||||
js_runtime
|
||||
.execute_script(
|
||||
"http_bench_json_ops.js",
|
||||
include_ascii_string!("http_bench_json_ops.js"),
|
||||
)
|
||||
.unwrap();
|
||||
js_runtime.run_event_loop(false).await
|
||||
};
|
||||
runtime.block_on(future).unwrap();
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
||||
//! This example shows that op-panics currently result in UB (likely "failed to initiate panic")
|
||||
//! without a custom panic hook that aborts the process or -C panic=abort.
|
||||
//!
|
||||
//! This happens due to the UB of panicking in an extern "C",
|
||||
//! given how ops are reduced via rusty_v8::MapFnTo
|
||||
//! See:
|
||||
//! - https://github.com/rust-lang/rust/issues/74990
|
||||
//! - https://rust-lang.github.io/rfcs/2945-c-unwind-abi.html
|
||||
|
||||
use deno_core::op;
|
||||
use deno_core::Extension;
|
||||
use deno_core::JsRuntime;
|
||||
use deno_core::RuntimeOptions;
|
||||
|
||||
// This is a hack to make the `#[op]` macro work with
|
||||
// deno_core examples.
|
||||
// You can remove this:
|
||||
use deno_core::*;
|
||||
|
||||
fn main() {
|
||||
#[op]
|
||||
fn op_panik() {
|
||||
panic!("panik !!!")
|
||||
}
|
||||
|
||||
let extensions = vec![Extension::builder("my_ext")
|
||||
.ops(vec![op_panik::decl()])
|
||||
.build()];
|
||||
let mut rt = JsRuntime::new(RuntimeOptions {
|
||||
extensions,
|
||||
..Default::default()
|
||||
});
|
||||
rt.execute_script_static("panik", "Deno.core.ops.op_panik()")
|
||||
.unwrap();
|
||||
}
|
|
@ -1,69 +0,0 @@
|
|||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
||||
|
||||
use deno_core::anyhow::Error;
|
||||
use deno_core::op;
|
||||
use deno_core::Extension;
|
||||
use deno_core::JsRuntime;
|
||||
use deno_core::OpState;
|
||||
use deno_core::RuntimeOptions;
|
||||
use futures::channel::mpsc;
|
||||
use futures::stream::StreamExt;
|
||||
use std::task::Poll;
|
||||
|
||||
// This is a hack to make the `#[op]` macro work with
|
||||
// deno_core examples.
|
||||
// You can remove this:
|
||||
use deno_core::*;
|
||||
|
||||
type Task = Box<dyn FnOnce()>;
|
||||
|
||||
fn main() {
|
||||
let my_ext = Extension::builder("my_ext")
|
||||
.ops(vec![op_schedule_task::decl()])
|
||||
.event_loop_middleware(|state_rc, cx| {
|
||||
let mut state = state_rc.borrow_mut();
|
||||
let recv = state.borrow_mut::<mpsc::UnboundedReceiver<Task>>();
|
||||
let mut ref_loop = false;
|
||||
while let Poll::Ready(Some(call)) = recv.poll_next_unpin(cx) {
|
||||
call();
|
||||
ref_loop = true; // `call` can callback into runtime and schedule new callbacks :-)
|
||||
}
|
||||
ref_loop
|
||||
})
|
||||
.state(move |state| {
|
||||
let (tx, rx) = mpsc::unbounded::<Task>();
|
||||
state.put(tx);
|
||||
state.put(rx);
|
||||
})
|
||||
.build();
|
||||
|
||||
// Initialize a runtime instance
|
||||
let mut js_runtime = JsRuntime::new(RuntimeOptions {
|
||||
extensions: vec![my_ext],
|
||||
..Default::default()
|
||||
});
|
||||
let runtime = tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let future = async move {
|
||||
// Schedule 10 tasks.
|
||||
js_runtime
|
||||
.execute_script_static(
|
||||
"<usage>",
|
||||
r#"for (let i = 1; i <= 10; i++) Deno.core.ops.op_schedule_task(i);"#,
|
||||
)
|
||||
.unwrap();
|
||||
js_runtime.run_event_loop(false).await
|
||||
};
|
||||
runtime.block_on(future).unwrap();
|
||||
}
|
||||
|
||||
#[op]
|
||||
fn op_schedule_task(state: &mut OpState, i: u8) -> Result<(), Error> {
|
||||
let tx = state.borrow_mut::<mpsc::UnboundedSender<Task>>();
|
||||
tx.unbounded_send(Box::new(move || println!("Hello, world! x{i}")))
|
||||
.expect("unbounded_send failed");
|
||||
Ok(())
|
||||
}
|
|
@ -1,128 +0,0 @@
|
|||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
||||
//! This example shows how to use swc to transpile TypeScript and JSX/TSX
|
||||
//! modules.
|
||||
//!
|
||||
//! It will only transpile, not typecheck (like Deno's `--no-check` flag).
|
||||
|
||||
use std::pin::Pin;
|
||||
use std::rc::Rc;
|
||||
|
||||
use anyhow::anyhow;
|
||||
use anyhow::bail;
|
||||
use anyhow::Context;
|
||||
use anyhow::Error;
|
||||
use deno_ast::MediaType;
|
||||
use deno_ast::ParseParams;
|
||||
use deno_ast::SourceTextInfo;
|
||||
use deno_core::error::AnyError;
|
||||
use deno_core::resolve_import;
|
||||
use deno_core::resolve_path;
|
||||
use deno_core::JsRuntime;
|
||||
use deno_core::ModuleLoader;
|
||||
use deno_core::ModuleSource;
|
||||
use deno_core::ModuleSourceFuture;
|
||||
use deno_core::ModuleSpecifier;
|
||||
use deno_core::ModuleType;
|
||||
use deno_core::ResolutionKind;
|
||||
use deno_core::RuntimeOptions;
|
||||
use futures::FutureExt;
|
||||
|
||||
struct TypescriptModuleLoader;
|
||||
|
||||
impl ModuleLoader for TypescriptModuleLoader {
|
||||
fn resolve(
|
||||
&self,
|
||||
specifier: &str,
|
||||
referrer: &str,
|
||||
_kind: ResolutionKind,
|
||||
) -> Result<ModuleSpecifier, Error> {
|
||||
Ok(resolve_import(specifier, referrer)?)
|
||||
}
|
||||
|
||||
fn load(
|
||||
&self,
|
||||
module_specifier: &ModuleSpecifier,
|
||||
_maybe_referrer: Option<&ModuleSpecifier>,
|
||||
_is_dyn_import: bool,
|
||||
) -> Pin<Box<ModuleSourceFuture>> {
|
||||
fn load(
|
||||
module_specifier: &ModuleSpecifier,
|
||||
) -> Result<ModuleSource, AnyError> {
|
||||
let path = module_specifier
|
||||
.to_file_path()
|
||||
.map_err(|_| anyhow!("Only file:// URLs are supported."))?;
|
||||
|
||||
let media_type = MediaType::from_path(&path);
|
||||
let (module_type, should_transpile) = match MediaType::from_path(&path) {
|
||||
MediaType::JavaScript | MediaType::Mjs | MediaType::Cjs => {
|
||||
(ModuleType::JavaScript, false)
|
||||
}
|
||||
MediaType::Jsx => (ModuleType::JavaScript, true),
|
||||
MediaType::TypeScript
|
||||
| MediaType::Mts
|
||||
| MediaType::Cts
|
||||
| MediaType::Dts
|
||||
| MediaType::Dmts
|
||||
| MediaType::Dcts
|
||||
| MediaType::Tsx => (ModuleType::JavaScript, true),
|
||||
MediaType::Json => (ModuleType::Json, false),
|
||||
_ => bail!("Unknown extension {:?}", path.extension()),
|
||||
};
|
||||
|
||||
let code = std::fs::read_to_string(&path)?;
|
||||
let code = if should_transpile {
|
||||
let parsed = deno_ast::parse_module(ParseParams {
|
||||
specifier: module_specifier.to_string(),
|
||||
text_info: SourceTextInfo::from_string(code),
|
||||
media_type,
|
||||
capture_tokens: false,
|
||||
scope_analysis: false,
|
||||
maybe_syntax: None,
|
||||
})?;
|
||||
parsed.transpile(&Default::default())?.text
|
||||
} else {
|
||||
code
|
||||
};
|
||||
Ok(ModuleSource::new(
|
||||
module_type,
|
||||
code.into(),
|
||||
module_specifier,
|
||||
))
|
||||
}
|
||||
|
||||
futures::future::ready(load(module_specifier)).boxed_local()
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Error> {
|
||||
let args: Vec<String> = std::env::args().collect();
|
||||
if args.len() < 2 {
|
||||
println!("Usage: target/examples/debug/ts_module_loader <path_to_module>");
|
||||
std::process::exit(1);
|
||||
}
|
||||
let main_url = &args[1];
|
||||
println!("Run {main_url}");
|
||||
|
||||
let mut js_runtime = JsRuntime::new(RuntimeOptions {
|
||||
module_loader: Some(Rc::new(TypescriptModuleLoader)),
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let main_module = resolve_path(
|
||||
main_url,
|
||||
&std::env::current_dir().context("Unable to get CWD")?,
|
||||
)?;
|
||||
|
||||
let future = async move {
|
||||
let mod_id = js_runtime.load_main_module(&main_module, None).await?;
|
||||
let result = js_runtime.mod_evaluate(mod_id);
|
||||
js_runtime.run_event_loop(false).await?;
|
||||
result.await?
|
||||
};
|
||||
|
||||
tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap()
|
||||
.block_on(future)
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
||||
|
||||
// asc wasm.ts --exportStart --initialMemory 6400 -O -o wasm.wasm
|
||||
// deno-fmt-ignore
|
||||
const bytes = new Uint8Array([
|
||||
0, 97, 115, 109, 1, 0, 0, 0, 1, 4, 1, 96, 0, 0, 2,
|
||||
15, 1, 3, 111, 112, 115, 7, 111, 112, 95, 119, 97, 115, 109, 0,
|
||||
0, 3, 3, 2, 0, 0, 5, 4, 1, 0, 128, 50, 7, 36, 4,
|
||||
7, 111, 112, 95, 119, 97, 115, 109, 0, 0, 4, 99, 97, 108, 108,
|
||||
0, 1, 6, 109, 101, 109, 111, 114, 121, 2, 0, 6, 95, 115, 116,
|
||||
97, 114, 116, 0, 2, 10, 10, 2, 4, 0, 16, 0, 11, 3, 0,
|
||||
1, 11
|
||||
]);
|
||||
|
||||
const { ops } = Deno.core;
|
||||
|
||||
const module = new WebAssembly.Module(bytes);
|
||||
const instance = new WebAssembly.Instance(module, { ops });
|
||||
ops.op_set_wasm_mem(instance.exports.memory);
|
||||
|
||||
instance.exports.call();
|
||||
|
||||
const memory = instance.exports.memory;
|
||||
const view = new Uint8Array(memory.buffer);
|
||||
|
||||
if (view[0] !== 69) {
|
||||
throw new Error("Expected first byte to be 69");
|
||||
}
|
|
@ -1,67 +0,0 @@
|
|||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
||||
|
||||
use deno_core::op;
|
||||
use deno_core::Extension;
|
||||
use deno_core::JsRuntime;
|
||||
use deno_core::RuntimeOptions;
|
||||
use std::mem::transmute;
|
||||
use std::ptr::NonNull;
|
||||
|
||||
// This is a hack to make the `#[op]` macro work with
|
||||
// deno_core examples.
|
||||
// You can remove this:
|
||||
|
||||
use deno_core::*;
|
||||
|
||||
struct WasmMemory(NonNull<v8::WasmMemoryObject>);
|
||||
|
||||
fn wasm_memory_unchecked(state: &mut OpState) -> &mut [u8] {
|
||||
let WasmMemory(global) = state.borrow::<WasmMemory>();
|
||||
// SAFETY: `v8::Local` is always non-null pointer; the `HandleScope` is
|
||||
// already on the stack, but we don't have access to it.
|
||||
let memory_object = unsafe {
|
||||
transmute::<NonNull<v8::WasmMemoryObject>, v8::Local<v8::WasmMemoryObject>>(
|
||||
*global,
|
||||
)
|
||||
};
|
||||
let backing_store = memory_object.buffer().get_backing_store();
|
||||
let ptr = backing_store.data().unwrap().as_ptr() as *mut u8;
|
||||
let len = backing_store.byte_length();
|
||||
// SAFETY: `ptr` is a valid pointer to `len` bytes.
|
||||
unsafe { std::slice::from_raw_parts_mut(ptr, len) }
|
||||
}
|
||||
|
||||
#[op(wasm)]
|
||||
fn op_wasm(state: &mut OpState, memory: Option<&mut [u8]>) {
|
||||
let memory = memory.unwrap_or_else(|| wasm_memory_unchecked(state));
|
||||
memory[0] = 69;
|
||||
}
|
||||
|
||||
#[op(v8)]
|
||||
fn op_set_wasm_mem(
|
||||
scope: &mut v8::HandleScope,
|
||||
state: &mut OpState,
|
||||
memory: serde_v8::Value,
|
||||
) {
|
||||
let memory =
|
||||
v8::Local::<v8::WasmMemoryObject>::try_from(memory.v8_value).unwrap();
|
||||
let global = v8::Global::new(scope, memory);
|
||||
state.put(WasmMemory(global.into_raw()));
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// Build a deno_core::Extension providing custom ops
|
||||
let ext = Extension::builder("my_ext")
|
||||
.ops(vec![op_wasm::decl(), op_set_wasm_mem::decl()])
|
||||
.build();
|
||||
|
||||
// Initialize a runtime instance
|
||||
let mut runtime = JsRuntime::new(RuntimeOptions {
|
||||
extensions: vec![ext],
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
runtime
|
||||
.execute_script("<usage>", include_ascii_string!("wasm.js"))
|
||||
.unwrap();
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
||||
|
||||
export declare function op_wasm(): void;
|
||||
|
||||
export function call(): void {
|
||||
op_wasm();
|
||||
}
|
|
@ -1,645 +0,0 @@
|
|||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
||||
use crate::modules::ModuleCode;
|
||||
use crate::OpState;
|
||||
use anyhow::Context as _;
|
||||
use anyhow::Error;
|
||||
use std::cell::RefCell;
|
||||
use std::path::PathBuf;
|
||||
use std::rc::Rc;
|
||||
use std::task::Context;
|
||||
use v8::fast_api::FastFunction;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum ExtensionFileSourceCode {
|
||||
/// Source code is included in the binary produced. Either by being defined
|
||||
/// inline, or included using `include_str!()`. If you are snapshotting, this
|
||||
/// will result in two copies of the source code being included - one in the
|
||||
/// snapshot, the other the static string in the `Extension`.
|
||||
IncludedInBinary(&'static str),
|
||||
|
||||
// Source code is loaded from a file on disk. It's meant to be used if the
|
||||
// embedder is creating snapshots. Files will be loaded from the filesystem
|
||||
// during the build time and they will only be present in the V8 snapshot.
|
||||
LoadedFromFsDuringSnapshot(PathBuf),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ExtensionFileSource {
|
||||
pub specifier: &'static str,
|
||||
pub code: ExtensionFileSourceCode,
|
||||
}
|
||||
|
||||
impl ExtensionFileSource {
|
||||
fn find_non_ascii(s: &str) -> String {
|
||||
s.chars().filter(|c| !c.is_ascii()).collect::<String>()
|
||||
}
|
||||
|
||||
pub fn load(&self) -> Result<ModuleCode, Error> {
|
||||
match &self.code {
|
||||
ExtensionFileSourceCode::IncludedInBinary(code) => {
|
||||
debug_assert!(
|
||||
code.is_ascii(),
|
||||
"Extension code must be 7-bit ASCII: {} (found {})",
|
||||
self.specifier,
|
||||
Self::find_non_ascii(code)
|
||||
);
|
||||
Ok(ModuleCode::from_static(code))
|
||||
}
|
||||
ExtensionFileSourceCode::LoadedFromFsDuringSnapshot(path) => {
|
||||
let msg = || format!("Failed to read \"{}\"", path.display());
|
||||
let s = std::fs::read_to_string(path).with_context(msg)?;
|
||||
debug_assert!(
|
||||
s.is_ascii(),
|
||||
"Extension code must be 7-bit ASCII: {} (found {})",
|
||||
self.specifier,
|
||||
Self::find_non_ascii(&s)
|
||||
);
|
||||
Ok(s.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub type OpFnRef = v8::FunctionCallback;
|
||||
pub type OpMiddlewareFn = dyn Fn(OpDecl) -> OpDecl;
|
||||
pub type OpStateFn = dyn FnOnce(&mut OpState);
|
||||
pub type OpEventLoopFn = dyn Fn(Rc<RefCell<OpState>>, &mut Context) -> bool;
|
||||
|
||||
/// Trait implemented by all generated ops.
|
||||
pub trait Op {
|
||||
const NAME: &'static str;
|
||||
const DECL: OpDecl;
|
||||
}
|
||||
|
||||
pub struct OpDecl {
|
||||
pub name: &'static str,
|
||||
pub v8_fn_ptr: OpFnRef,
|
||||
pub enabled: bool,
|
||||
pub is_async: bool,
|
||||
pub is_unstable: bool,
|
||||
pub is_v8: bool,
|
||||
pub arg_count: u8,
|
||||
pub fast_fn: Option<FastFunction>,
|
||||
}
|
||||
|
||||
impl OpDecl {
|
||||
pub fn enabled(self, enabled: bool) -> Self {
|
||||
Self { enabled, ..self }
|
||||
}
|
||||
|
||||
pub fn disable(self) -> Self {
|
||||
self.enabled(false)
|
||||
}
|
||||
}
|
||||
|
||||
/// Declares a block of Deno `#[op]`s. The first parameter determines the name of the
|
||||
/// op declaration block, and is usually `deno_ops`. This block generates a function that
|
||||
/// returns a [`Vec<OpDecl>`].
|
||||
///
|
||||
/// This can be either a compact form like:
|
||||
///
|
||||
/// ```no_compile
|
||||
/// # use deno_core::*;
|
||||
/// #[op]
|
||||
/// fn op_xyz() {}
|
||||
///
|
||||
/// deno_core::ops!(deno_ops, [
|
||||
/// op_xyz
|
||||
/// ]);
|
||||
///
|
||||
/// // Use the ops:
|
||||
/// deno_ops()
|
||||
/// ```
|
||||
///
|
||||
/// ... or a parameterized form like so that allows passing a number of type parameters
|
||||
/// to each `#[op]`:
|
||||
///
|
||||
/// ```no_compile
|
||||
/// # use deno_core::*;
|
||||
/// #[op]
|
||||
/// fn op_xyz<P>() where P: Clone {}
|
||||
///
|
||||
/// deno_core::ops!(deno_ops,
|
||||
/// parameters = [P: Clone],
|
||||
/// ops = [
|
||||
/// op_xyz<P>
|
||||
/// ]
|
||||
/// );
|
||||
///
|
||||
/// // Use the ops, with `String` as the parameter `P`:
|
||||
/// deno_ops::<String>()
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! ops {
|
||||
($name:ident, parameters = [ $( $param:ident : $type:ident ),+ ], ops = [ $( $(#[$m:meta])* $( $op:ident )::+ $( < $op_param:ident > )? ),+ $(,)? ]) => {
|
||||
pub(crate) fn $name < $( $param : $type + 'static ),+ > () -> Vec<$crate::OpDecl> {
|
||||
vec![
|
||||
$(
|
||||
$( #[ $m ] )*
|
||||
$( $op )::+ :: decl $( :: <$op_param> )? () ,
|
||||
)+
|
||||
]
|
||||
}
|
||||
};
|
||||
($name:ident, [ $( $(#[$m:meta])* $( $op:ident )::+ ),+ $(,)? ] ) => {
|
||||
pub(crate) fn $name() -> Vec<$crate::OpDecl> {
|
||||
vec![
|
||||
$( $( #[ $m ] )* $( $op )::+ :: decl(), )+
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Defines a Deno extension. The first parameter is the name of the extension symbol namespace to create. This is the symbol you
|
||||
/// will use to refer to the extension.
|
||||
///
|
||||
/// Most extensions will define a combination of ops and ESM files, like so:
|
||||
///
|
||||
/// ```no_compile
|
||||
/// #[op]
|
||||
/// fn op_xyz() {
|
||||
/// }
|
||||
///
|
||||
/// deno_core::extension!(
|
||||
/// my_extension,
|
||||
/// ops = [ op_xyz ],
|
||||
/// esm = [ "my_script.js" ],
|
||||
/// );
|
||||
/// ```
|
||||
///
|
||||
/// The following options are available for the [`extension`] macro:
|
||||
///
|
||||
/// * deps: a comma-separated list of module dependencies, eg: `deps = [ my_other_extension ]`
|
||||
/// * parameters: a comma-separated list of parameters and base traits, eg: `parameters = [ P: MyTrait ]`
|
||||
/// * bounds: a comma-separated list of additional type bounds, eg: `bounds = [ P::MyAssociatedType: MyTrait ]`
|
||||
/// * ops: a comma-separated list of [`OpDecl`]s to provide, eg: `ops = [ op_foo, op_bar ]`
|
||||
/// * esm: a comma-separated list of ESM module filenames (see [`include_js_files`]), eg: `esm = [ dir "dir", "my_file.js" ]`
|
||||
/// * js: a comma-separated list of JS filenames (see [`include_js_files`]), eg: `js = [ dir "dir", "my_file.js" ]`
|
||||
/// * config: a structure-like definition for configuration parameters which will be required when initializing this extension, eg: `config = { my_param: Option<usize> }`
|
||||
/// * middleware: an [`OpDecl`] middleware function with the signature `fn (OpDecl) -> OpDecl`
|
||||
/// * state: a state initialization function, with the signature `fn (&mut OpState, ...) -> ()`, where `...` are parameters matching the fields of the config struct
|
||||
/// * event_loop_middleware: an event-loop middleware function (see [`ExtensionBuilder::event_loop_middleware`])
|
||||
#[macro_export]
|
||||
macro_rules! extension {
|
||||
(
|
||||
$name:ident
|
||||
$(, deps = [ $( $dep:ident ),* ] )?
|
||||
$(, parameters = [ $( $param:ident : $type:ident ),+ ] )?
|
||||
$(, bounds = [ $( $bound:path : $bound_type:ident ),+ ] )?
|
||||
$(, ops_fn = $ops_symbol:ident $( < $ops_param:ident > )? )?
|
||||
$(, ops = [ $( $(#[$m:meta])* $( $op:ident )::+ $( < $( $op_param:ident ),* > )? ),+ $(,)? ] )?
|
||||
$(, esm_entry_point = $esm_entry_point:literal )?
|
||||
$(, esm = [ $( dir $dir_esm:literal , )? $( $esm:literal ),* $(,)? ] )?
|
||||
$(, js = [ $( dir $dir_js:literal , )? $( $js:literal ),* $(,)? ] )?
|
||||
$(, options = { $( $options_id:ident : $options_type:ty ),* $(,)? } )?
|
||||
$(, middleware = $middleware_fn:expr )?
|
||||
$(, state = $state_fn:expr )?
|
||||
$(, event_loop_middleware = $event_loop_middleware_fn:ident )?
|
||||
$(, customizer = $customizer_fn:expr )?
|
||||
$(,)?
|
||||
) => {
|
||||
/// Extension struct for
|
||||
#[doc = stringify!($name)]
|
||||
/// .
|
||||
#[allow(non_camel_case_types)]
|
||||
pub struct $name {
|
||||
}
|
||||
|
||||
impl $name {
|
||||
#[inline(always)]
|
||||
fn ext() -> $crate::ExtensionBuilder {
|
||||
$crate::Extension::builder_with_deps(stringify!($name), &[ $( $( stringify!($dep) ),* )? ])
|
||||
}
|
||||
|
||||
/// If ESM or JS was specified, add those files to the extension.
|
||||
#[inline(always)]
|
||||
#[allow(unused_variables)]
|
||||
fn with_js(ext: &mut $crate::ExtensionBuilder) {
|
||||
$( ext.esm(
|
||||
$crate::include_js_files!( $name $( dir $dir_esm , )? $( $esm , )* )
|
||||
); )?
|
||||
$(
|
||||
ext.esm_entry_point($esm_entry_point);
|
||||
)?
|
||||
$( ext.js(
|
||||
$crate::include_js_files!( $name $( dir $dir_js , )? $( $js , )* )
|
||||
); )?
|
||||
}
|
||||
|
||||
// If ops were specified, add those ops to the extension.
|
||||
#[inline(always)]
|
||||
#[allow(unused_variables)]
|
||||
fn with_ops $( < $( $param : $type + 'static ),+ > )?(ext: &mut $crate::ExtensionBuilder)
|
||||
$( where $( $bound : $bound_type ),+ )?
|
||||
{
|
||||
// If individual ops are specified, roll them up into a vector and apply them
|
||||
$(
|
||||
ext.ops(vec![
|
||||
$(
|
||||
$( #[ $m ] )*
|
||||
$( $op )::+ $( :: < $($op_param),* > )? :: decl ()
|
||||
),+
|
||||
]);
|
||||
)?
|
||||
|
||||
// Otherwise use the ops_fn, if provided
|
||||
$crate::extension!(! __ops__ ext $( $ops_symbol $( < $ops_param > )? )? __eot__);
|
||||
}
|
||||
|
||||
// Includes the state and middleware functions, if defined.
|
||||
#[inline(always)]
|
||||
#[allow(unused_variables)]
|
||||
fn with_state_and_middleware$( < $( $param : $type + 'static ),+ > )?(ext: &mut $crate::ExtensionBuilder, $( $( $options_id : $options_type ),* )? )
|
||||
$( where $( $bound : $bound_type ),+ )?
|
||||
{
|
||||
$crate::extension!(! __config__ ext $( parameters = [ $( $param : $type ),* ] )? $( config = { $( $options_id : $options_type ),* } )? $( state_fn = $state_fn )? );
|
||||
|
||||
$(
|
||||
ext.event_loop_middleware($event_loop_middleware_fn);
|
||||
)?
|
||||
|
||||
$(
|
||||
ext.middleware($middleware_fn);
|
||||
)?
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
#[allow(unused_variables)]
|
||||
#[allow(clippy::redundant_closure_call)]
|
||||
fn with_customizer(ext: &mut $crate::ExtensionBuilder) {
|
||||
$( ($customizer_fn)(ext); )?
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn init_js_only $( < $( $param : $type + 'static ),* > )? () -> $crate::Extension
|
||||
$( where $( $bound : $bound_type ),+ )?
|
||||
{
|
||||
let mut ext = Self::ext();
|
||||
// If esm or JS was specified, add JS files
|
||||
Self::with_js(&mut ext);
|
||||
Self::with_ops $( ::< $( $param ),+ > )?(&mut ext);
|
||||
Self::with_customizer(&mut ext);
|
||||
ext.take()
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn init_ops_and_esm $( < $( $param : $type + 'static ),+ > )? ( $( $( $options_id : $options_type ),* )? ) -> $crate::Extension
|
||||
$( where $( $bound : $bound_type ),+ )?
|
||||
{
|
||||
let mut ext = Self::ext();
|
||||
// If esm or JS was specified, add JS files
|
||||
Self::with_js(&mut ext);
|
||||
Self::with_ops $( ::< $( $param ),+ > )?(&mut ext);
|
||||
Self::with_state_and_middleware $( ::< $( $param ),+ > )?(&mut ext, $( $( $options_id , )* )? );
|
||||
Self::with_customizer(&mut ext);
|
||||
ext.take()
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn init_ops $( < $( $param : $type + 'static ),+ > )? ( $( $( $options_id : $options_type ),* )? ) -> $crate::Extension
|
||||
$( where $( $bound : $bound_type ),+ )?
|
||||
{
|
||||
let mut ext = Self::ext();
|
||||
Self::with_ops $( ::< $( $param ),+ > )?(&mut ext);
|
||||
Self::with_state_and_middleware $( ::< $( $param ),+ > )?(&mut ext, $( $( $options_id , )* )? );
|
||||
Self::with_customizer(&mut ext);
|
||||
ext.take()
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// This branch of the macro generates a config object that calls the state function with itself.
|
||||
(! __config__ $ext:ident $( parameters = [ $( $param:ident : $type:ident ),+ ] )? config = { $( $options_id:ident : $options_type:ty ),* } $( state_fn = $state_fn:expr )? ) => {
|
||||
{
|
||||
#[doc(hidden)]
|
||||
struct Config $( < $( $param : $type + 'static ),+ > )? {
|
||||
$( pub $options_id : $options_type , )*
|
||||
$( __phantom_data: ::std::marker::PhantomData<($( $param ),+)>, )?
|
||||
}
|
||||
let config = Config {
|
||||
$( $options_id , )*
|
||||
$( __phantom_data: ::std::marker::PhantomData::<($( $param ),+)>::default() )?
|
||||
};
|
||||
|
||||
let state_fn: fn(&mut $crate::OpState, Config $( < $( $param ),+ > )? ) = $( $state_fn )?;
|
||||
$ext.state(move |state: &mut $crate::OpState| {
|
||||
state_fn(state, config);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
(! __config__ $ext:ident $( parameters = [ $( $param:ident : $type:ident ),+ ] )? $( state_fn = $state_fn:expr )? ) => {
|
||||
$( $ext.state($state_fn); )?
|
||||
};
|
||||
|
||||
(! __ops__ $ext:ident __eot__) => {
|
||||
};
|
||||
|
||||
(! __ops__ $ext:ident $ops_symbol:ident __eot__) => {
|
||||
$ext.ops($ops_symbol())
|
||||
};
|
||||
|
||||
(! __ops__ $ext:ident $ops_symbol:ident < $ops_param:ident > __eot__) => {
|
||||
$ext.ops($ops_symbol::<$ops_param>())
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Extension {
|
||||
pub(crate) name: &'static str,
|
||||
js_files: Vec<ExtensionFileSource>,
|
||||
esm_files: Vec<ExtensionFileSource>,
|
||||
esm_entry_point: Option<&'static str>,
|
||||
ops: Option<Vec<OpDecl>>,
|
||||
opstate_fn: Option<Box<OpStateFn>>,
|
||||
middleware_fn: Option<Box<OpMiddlewareFn>>,
|
||||
event_loop_middleware: Option<Box<OpEventLoopFn>>,
|
||||
initialized: bool,
|
||||
enabled: bool,
|
||||
deps: Option<&'static [&'static str]>,
|
||||
}
|
||||
|
||||
// Note: this used to be a trait, but we "downgraded" it to a single concrete type
|
||||
// for the initial iteration, it will likely become a trait in the future
|
||||
impl Extension {
|
||||
pub fn builder(name: &'static str) -> ExtensionBuilder {
|
||||
ExtensionBuilder {
|
||||
name,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn builder_with_deps(
|
||||
name: &'static str,
|
||||
deps: &'static [&'static str],
|
||||
) -> ExtensionBuilder {
|
||||
ExtensionBuilder {
|
||||
name,
|
||||
deps,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if dependencies have been loaded, and errors if either:
|
||||
/// - The extension is depending on itself or an extension with the same name.
|
||||
/// - A dependency hasn't been loaded yet.
|
||||
pub fn check_dependencies(&self, previous_exts: &[Extension]) {
|
||||
if let Some(deps) = self.deps {
|
||||
'dep_loop: for dep in deps {
|
||||
if dep == &self.name {
|
||||
panic!("Extension '{}' is either depending on itself or there is another extension with the same name", self.name);
|
||||
}
|
||||
|
||||
for ext in previous_exts {
|
||||
if dep == &ext.name {
|
||||
continue 'dep_loop;
|
||||
}
|
||||
}
|
||||
|
||||
panic!("Extension '{}' is missing dependency '{dep}'", self.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// returns JS source code to be loaded into the isolate (either at snapshotting,
|
||||
/// or at startup). as a vector of a tuple of the file name, and the source code.
|
||||
pub fn get_js_sources(&self) -> &Vec<ExtensionFileSource> {
|
||||
&self.js_files
|
||||
}
|
||||
|
||||
pub fn get_esm_sources(&self) -> &Vec<ExtensionFileSource> {
|
||||
&self.esm_files
|
||||
}
|
||||
|
||||
pub fn get_esm_entry_point(&self) -> Option<&'static str> {
|
||||
self.esm_entry_point
|
||||
}
|
||||
|
||||
/// Called at JsRuntime startup to initialize ops in the isolate.
|
||||
pub fn init_ops(&mut self) -> Option<Vec<OpDecl>> {
|
||||
// TODO(@AaronO): maybe make op registration idempotent
|
||||
if self.initialized {
|
||||
panic!("init_ops called twice: not idempotent or correct");
|
||||
}
|
||||
self.initialized = true;
|
||||
|
||||
let mut ops = self.ops.take()?;
|
||||
for op in ops.iter_mut() {
|
||||
op.enabled = self.enabled && op.enabled;
|
||||
}
|
||||
Some(ops)
|
||||
}
|
||||
|
||||
/// Allows setting up the initial op-state of an isolate at startup.
|
||||
pub fn init_state(&mut self, state: &mut OpState) {
|
||||
if let Some(op_fn) = self.opstate_fn.take() {
|
||||
op_fn(state);
|
||||
}
|
||||
}
|
||||
|
||||
/// init_middleware lets us middleware op registrations, it's called before init_ops
|
||||
pub fn init_middleware(&mut self) -> Option<Box<OpMiddlewareFn>> {
|
||||
self.middleware_fn.take()
|
||||
}
|
||||
|
||||
pub fn init_event_loop_middleware(&mut self) -> Option<Box<OpEventLoopFn>> {
|
||||
self.event_loop_middleware.take()
|
||||
}
|
||||
|
||||
pub fn run_event_loop_middleware(
|
||||
&self,
|
||||
op_state_rc: Rc<RefCell<OpState>>,
|
||||
cx: &mut Context,
|
||||
) -> bool {
|
||||
self
|
||||
.event_loop_middleware
|
||||
.as_ref()
|
||||
.map(|f| f(op_state_rc, cx))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub fn enabled(self, enabled: bool) -> Self {
|
||||
Self { enabled, ..self }
|
||||
}
|
||||
|
||||
pub fn disable(self) -> Self {
|
||||
self.enabled(false)
|
||||
}
|
||||
}
|
||||
|
||||
// Provides a convenient builder pattern to declare Extensions
|
||||
#[derive(Default)]
|
||||
pub struct ExtensionBuilder {
|
||||
js: Vec<ExtensionFileSource>,
|
||||
esm: Vec<ExtensionFileSource>,
|
||||
esm_entry_point: Option<&'static str>,
|
||||
ops: Vec<OpDecl>,
|
||||
state: Option<Box<OpStateFn>>,
|
||||
middleware: Option<Box<OpMiddlewareFn>>,
|
||||
event_loop_middleware: Option<Box<OpEventLoopFn>>,
|
||||
name: &'static str,
|
||||
deps: &'static [&'static str],
|
||||
}
|
||||
|
||||
impl ExtensionBuilder {
|
||||
pub fn js(&mut self, js_files: Vec<ExtensionFileSource>) -> &mut Self {
|
||||
self.js.extend(js_files);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn esm(&mut self, esm_files: Vec<ExtensionFileSource>) -> &mut Self {
|
||||
self.esm.extend(esm_files);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn esm_entry_point(&mut self, entry_point: &'static str) -> &mut Self {
|
||||
self.esm_entry_point = Some(entry_point);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn ops(&mut self, ops: Vec<OpDecl>) -> &mut Self {
|
||||
self.ops.extend(ops);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn state<F>(&mut self, opstate_fn: F) -> &mut Self
|
||||
where
|
||||
F: FnOnce(&mut OpState) + 'static,
|
||||
{
|
||||
self.state = Some(Box::new(opstate_fn));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn middleware<F>(&mut self, middleware_fn: F) -> &mut Self
|
||||
where
|
||||
F: Fn(OpDecl) -> OpDecl + 'static,
|
||||
{
|
||||
self.middleware = Some(Box::new(middleware_fn));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn event_loop_middleware<F>(&mut self, middleware_fn: F) -> &mut Self
|
||||
where
|
||||
F: Fn(Rc<RefCell<OpState>>, &mut Context) -> bool + 'static,
|
||||
{
|
||||
self.event_loop_middleware = Some(Box::new(middleware_fn));
|
||||
self
|
||||
}
|
||||
|
||||
/// Consume the [`ExtensionBuilder`] and return an [`Extension`].
|
||||
pub fn take(self) -> Extension {
|
||||
let ops = Some(self.ops);
|
||||
let deps = Some(self.deps);
|
||||
Extension {
|
||||
js_files: self.js,
|
||||
esm_files: self.esm,
|
||||
esm_entry_point: self.esm_entry_point,
|
||||
ops,
|
||||
opstate_fn: self.state,
|
||||
middleware_fn: self.middleware,
|
||||
event_loop_middleware: self.event_loop_middleware,
|
||||
initialized: false,
|
||||
enabled: true,
|
||||
name: self.name,
|
||||
deps,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build(&mut self) -> Extension {
|
||||
let ops = Some(std::mem::take(&mut self.ops));
|
||||
let deps = Some(std::mem::take(&mut self.deps));
|
||||
Extension {
|
||||
js_files: std::mem::take(&mut self.js),
|
||||
esm_files: std::mem::take(&mut self.esm),
|
||||
esm_entry_point: self.esm_entry_point.take(),
|
||||
ops,
|
||||
opstate_fn: self.state.take(),
|
||||
middleware_fn: self.middleware.take(),
|
||||
event_loop_middleware: self.event_loop_middleware.take(),
|
||||
initialized: false,
|
||||
enabled: true,
|
||||
name: self.name,
|
||||
deps,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Helps embed JS files in an extension. Returns a vector of
|
||||
/// `ExtensionFileSource`, that represent the filename and source code. All
|
||||
/// specified files are rewritten into "ext:<extension_name>/<file_name>".
|
||||
///
|
||||
/// An optional "dir" option can be specified to prefix all files with a
|
||||
/// directory name.
|
||||
///
|
||||
/// Example (for "my_extension"):
|
||||
/// ```ignore
|
||||
/// include_js_files!(
|
||||
/// "01_hello.js",
|
||||
/// "02_goodbye.js",
|
||||
/// )
|
||||
/// // Produces following specifiers:
|
||||
/// - "ext:my_extension/01_hello.js"
|
||||
/// - "ext:my_extension/02_goodbye.js"
|
||||
///
|
||||
/// /// Example with "dir" option (for "my_extension"):
|
||||
/// ```ignore
|
||||
/// include_js_files!(
|
||||
/// dir "js",
|
||||
/// "01_hello.js",
|
||||
/// "02_goodbye.js",
|
||||
/// )
|
||||
/// // Produces following specifiers:
|
||||
/// - "ext:my_extension/js/01_hello.js"
|
||||
/// - "ext:my_extension/js/02_goodbye.js"
|
||||
/// ```
|
||||
#[cfg(not(feature = "include_js_files_for_snapshotting"))]
|
||||
#[macro_export]
|
||||
macro_rules! include_js_files {
|
||||
($name:ident dir $dir:literal, $($file:literal,)+) => {
|
||||
vec![
|
||||
$($crate::ExtensionFileSource {
|
||||
specifier: concat!("ext:", stringify!($name), "/", $file),
|
||||
code: $crate::ExtensionFileSourceCode::IncludedInBinary(
|
||||
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/", $dir, "/", $file))
|
||||
),
|
||||
},)+
|
||||
]
|
||||
};
|
||||
|
||||
($name:ident $($file:literal,)+) => {
|
||||
vec![
|
||||
$($crate::ExtensionFileSource {
|
||||
specifier: concat!("ext:", stringify!($name), "/", $file),
|
||||
code: $crate::ExtensionFileSourceCode::IncludedInBinary(
|
||||
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/", $file))
|
||||
),
|
||||
},)+
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(feature = "include_js_files_for_snapshotting")]
|
||||
#[macro_export]
|
||||
macro_rules! include_js_files {
|
||||
($name:ident dir $dir:literal, $($file:literal,)+) => {
|
||||
vec![
|
||||
$($crate::ExtensionFileSource {
|
||||
specifier: concat!("ext:", stringify!($name), "/", $file),
|
||||
code: $crate::ExtensionFileSourceCode::LoadedFromFsDuringSnapshot(
|
||||
std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join($dir).join($file)
|
||||
),
|
||||
},)+
|
||||
]
|
||||
};
|
||||
|
||||
($name:ident $($file:literal,)+) => {
|
||||
vec![
|
||||
$($crate::ExtensionFileSource {
|
||||
specifier: concat!("ext:", stringify!($name), "/", $file),
|
||||
code: $crate::ExtensionFileSourceCode::LoadedFromFsDuringSnapshot(
|
||||
std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join($file)
|
||||
),
|
||||
},)+
|
||||
]
|
||||
};
|
||||
}
|
|
@ -1,243 +0,0 @@
|
|||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
||||
|
||||
use std::borrow::Borrow;
|
||||
use std::fmt::Debug;
|
||||
use std::hash::Hash;
|
||||
use std::sync::Arc;
|
||||
use url::Url;
|
||||
use v8::NewStringType;
|
||||
|
||||
/// Module names and code can be sourced from strings or bytes that are either owned or borrowed. This enumeration allows us
|
||||
/// to perform a minimal amount of cloning and format-shifting of the underlying data.
|
||||
///
|
||||
/// Note that any [`FastString`] created from a `'static` byte array or string must contain ASCII characters.
|
||||
///
|
||||
/// Examples of ways to construct a [`FastString`]:
|
||||
///
|
||||
/// ```rust
|
||||
/// # use deno_core::{ascii_str, FastString};
|
||||
///
|
||||
/// let code: FastString = ascii_str!("a string");
|
||||
/// let code: FastString = format!("a string").into();
|
||||
/// ```
|
||||
pub enum FastString {
|
||||
/// Created from static data.
|
||||
Static(&'static str),
|
||||
|
||||
/// Created from static data, known to contain only ASCII chars.
|
||||
StaticAscii(&'static str),
|
||||
|
||||
/// An owned chunk of data. Note that we use `Box` rather than `Vec` to avoid the
|
||||
/// storage overhead.
|
||||
Owned(Box<str>),
|
||||
|
||||
// Scripts loaded from the `deno_graph` infrastructure.
|
||||
Arc(Arc<str>),
|
||||
}
|
||||
|
||||
impl FastString {
|
||||
/// Compile-time function to determine if a string is ASCII. Note that UTF-8 chars
|
||||
/// longer than one byte have the high-bit set and thus, are not ASCII.
|
||||
const fn is_ascii(s: &'static [u8]) -> bool {
|
||||
let mut i = 0;
|
||||
while i < s.len() {
|
||||
if !s[i].is_ascii() {
|
||||
return false;
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
/// Create a [`FastString`] from a static string. The string may contain non-ASCII characters, and if
|
||||
/// so, will take the slower path when used in v8.
|
||||
pub const fn from_static(s: &'static str) -> Self {
|
||||
if Self::is_ascii(s.as_bytes()) {
|
||||
Self::StaticAscii(s)
|
||||
} else {
|
||||
Self::Static(s)
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a [`FastString`] from a static string. If the string contains non-ASCII characters, the compiler
|
||||
/// will abort.
|
||||
pub const fn ensure_static_ascii(s: &'static str) -> Self {
|
||||
if Self::is_ascii(s.as_bytes()) {
|
||||
Self::StaticAscii(s)
|
||||
} else {
|
||||
panic!("This string contained non-ASCII characters and cannot be created with ensure_static_ascii")
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a cheap copy of this [`FastString`], potentially transmuting it to a faster form. Note that this
|
||||
/// is not a clone operation as it consumes the old [`FastString`].
|
||||
pub fn into_cheap_copy(self) -> (Self, Self) {
|
||||
match self {
|
||||
Self::Static(s) => (Self::Static(s), Self::Static(s)),
|
||||
Self::StaticAscii(s) => (Self::StaticAscii(s), Self::StaticAscii(s)),
|
||||
Self::Arc(s) => (Self::Arc(s.clone()), Self::Arc(s)),
|
||||
Self::Owned(s) => {
|
||||
let s: Arc<str> = s.into();
|
||||
(Self::Arc(s.clone()), Self::Arc(s))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn try_static_ascii(&self) -> Option<&'static [u8]> {
|
||||
match self {
|
||||
Self::StaticAscii(s) => Some(s.as_bytes()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_bytes(&self) -> &[u8] {
|
||||
// TODO(mmastrac): This can be const eventually (waiting for Arc const deref)
|
||||
match self {
|
||||
Self::Arc(s) => s.as_bytes(),
|
||||
Self::Owned(s) => s.as_bytes(),
|
||||
Self::Static(s) => s.as_bytes(),
|
||||
Self::StaticAscii(s) => s.as_bytes(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_str(&self) -> &str {
|
||||
// TODO(mmastrac): This can be const eventually (waiting for Arc const deref)
|
||||
match self {
|
||||
Self::Arc(s) => s,
|
||||
Self::Owned(s) => s,
|
||||
Self::Static(s) => s,
|
||||
Self::StaticAscii(s) => s,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a v8 string from this [`FastString`]. If the string is static and contains only ASCII characters,
|
||||
/// an external one-byte static is created.
|
||||
pub fn v8<'a>(
|
||||
&self,
|
||||
scope: &mut v8::HandleScope<'a>,
|
||||
) -> v8::Local<'a, v8::String> {
|
||||
match self.try_static_ascii() {
|
||||
Some(s) => v8::String::new_external_onebyte_static(scope, s).unwrap(),
|
||||
None => {
|
||||
v8::String::new_from_utf8(scope, self.as_bytes(), NewStringType::Normal)
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Truncates a [`FastString`] value, possibly re-allocating or memcpy'ing. May be slow.
|
||||
pub fn truncate(&mut self, index: usize) {
|
||||
match self {
|
||||
Self::Static(b) => *self = Self::Static(&b[..index]),
|
||||
Self::StaticAscii(b) => *self = Self::StaticAscii(&b[..index]),
|
||||
Self::Owned(b) => *self = Self::Owned(b[..index].to_owned().into()),
|
||||
// We can't do much if we have an Arc<str>, so we'll just take ownership of the truncated version
|
||||
Self::Arc(s) => *self = s[..index].to_owned().into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Hash for FastString {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
self.as_str().hash(state)
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<str> for FastString {
|
||||
fn as_ref(&self) -> &str {
|
||||
self.as_str()
|
||||
}
|
||||
}
|
||||
|
||||
impl Borrow<str> for FastString {
|
||||
fn borrow(&self) -> &str {
|
||||
self.as_str()
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for FastString {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
Debug::fmt(self.as_str(), f)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for FastString {
|
||||
fn default() -> Self {
|
||||
Self::StaticAscii("")
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for FastString {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.as_bytes() == other.as_bytes()
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for FastString {}
|
||||
|
||||
/// [`FastString`] can be made cheaply from [`Url`] as we know it's owned and don't need to do an
|
||||
/// ASCII check.
|
||||
impl From<Url> for FastString {
|
||||
fn from(value: Url) -> Self {
|
||||
let s: String = value.into();
|
||||
s.into()
|
||||
}
|
||||
}
|
||||
|
||||
/// [`FastString`] can be made cheaply from [`String`] as we know it's owned and don't need to do an
|
||||
/// ASCII check.
|
||||
impl From<String> for FastString {
|
||||
fn from(value: String) -> Self {
|
||||
FastString::Owned(value.into_boxed_str())
|
||||
}
|
||||
}
|
||||
|
||||
/// [`FastString`] can be made cheaply from [`Arc<str>`] as we know it's shared and don't need to do an
|
||||
/// ASCII check.
|
||||
impl From<Arc<str>> for FastString {
|
||||
fn from(value: Arc<str>) -> Self {
|
||||
FastString::Arc(value)
|
||||
}
|
||||
}
|
||||
|
||||
/// Include a fast string in the binary. This string is asserted at compile-time to be 7-bit ASCII for optimal
|
||||
/// v8 performance.
|
||||
#[macro_export]
|
||||
macro_rules! include_ascii_string {
|
||||
($file:literal) => {
|
||||
$crate::FastString::ensure_static_ascii(include_str!($file))
|
||||
};
|
||||
}
|
||||
|
||||
/// Include a fast string in the binary from a string literal. This string is asserted at compile-time to be
|
||||
/// 7-bit ASCII for optimal v8 performance.
|
||||
#[macro_export]
|
||||
macro_rules! ascii_str {
|
||||
($str:literal) => {
|
||||
$crate::FastString::ensure_static_ascii($str)
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn truncate() {
|
||||
let mut s = "123456".to_owned();
|
||||
s.truncate(3);
|
||||
|
||||
let mut code: FastString = FastString::from_static("123456");
|
||||
code.truncate(3);
|
||||
assert_eq!(s, code.as_ref());
|
||||
|
||||
let mut code: FastString = "123456".to_owned().into();
|
||||
code.truncate(3);
|
||||
assert_eq!(s, code.as_ref());
|
||||
|
||||
let arc_str: Arc<str> = "123456".into();
|
||||
let mut code: FastString = arc_str.into();
|
||||
code.truncate(3);
|
||||
assert_eq!(s, code.as_ref());
|
||||
}
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
||||
|
||||
/// Pass the command line arguments to v8.
|
||||
/// Returns a vector of command line arguments that V8 did not understand.
|
||||
pub fn v8_set_flags(args: Vec<String>) -> Vec<String> {
|
||||
v8::V8::set_flags_from_command_line(args)
|
||||
}
|
|
@ -1,193 +0,0 @@
|
|||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
||||
// Forked from Gotham:
|
||||
// https://github.com/gotham-rs/gotham/blob/bcbbf8923789e341b7a0e62c59909428ca4e22e2/gotham/src/state/mod.rs
|
||||
// Copyright 2017 Gotham Project Developers. MIT license.
|
||||
|
||||
use log::trace;
|
||||
use std::any::type_name;
|
||||
use std::any::Any;
|
||||
use std::any::TypeId;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct GothamState {
|
||||
data: BTreeMap<TypeId, Box<dyn Any>>,
|
||||
}
|
||||
|
||||
impl GothamState {
|
||||
/// Puts a value into the `GothamState` storage. One value of each type is retained.
|
||||
/// Successive calls to `put` will overwrite the existing value of the same
|
||||
/// type.
|
||||
pub fn put<T: 'static>(&mut self, t: T) {
|
||||
let type_id = TypeId::of::<T>();
|
||||
trace!(" inserting record to state for type_id `{:?}`", type_id);
|
||||
self.data.insert(type_id, Box::new(t));
|
||||
}
|
||||
|
||||
/// Determines if the current value exists in `GothamState` storage.
|
||||
pub fn has<T: 'static>(&self) -> bool {
|
||||
let type_id = TypeId::of::<T>();
|
||||
self.data.get(&type_id).is_some()
|
||||
}
|
||||
|
||||
/// Tries to borrow a value from the `GothamState` storage.
|
||||
pub fn try_borrow<T: 'static>(&self) -> Option<&T> {
|
||||
let type_id = TypeId::of::<T>();
|
||||
trace!(" borrowing state data for type_id `{:?}`", type_id);
|
||||
self.data.get(&type_id).and_then(|b| b.downcast_ref())
|
||||
}
|
||||
|
||||
/// Borrows a value from the `GothamState` storage.
|
||||
pub fn borrow<T: 'static>(&self) -> &T {
|
||||
self.try_borrow().unwrap_or_else(|| missing::<T>())
|
||||
}
|
||||
|
||||
/// Tries to mutably borrow a value from the `GothamState` storage.
|
||||
pub fn try_borrow_mut<T: 'static>(&mut self) -> Option<&mut T> {
|
||||
let type_id = TypeId::of::<T>();
|
||||
trace!(" mutably borrowing state data for type_id `{:?}`", type_id);
|
||||
self.data.get_mut(&type_id).and_then(|b| b.downcast_mut())
|
||||
}
|
||||
|
||||
/// Mutably borrows a value from the `GothamState` storage.
|
||||
pub fn borrow_mut<T: 'static>(&mut self) -> &mut T {
|
||||
self.try_borrow_mut().unwrap_or_else(|| missing::<T>())
|
||||
}
|
||||
|
||||
/// Tries to move a value out of the `GothamState` storage and return ownership.
|
||||
pub fn try_take<T: 'static>(&mut self) -> Option<T> {
|
||||
let type_id = TypeId::of::<T>();
|
||||
trace!(
|
||||
" taking ownership from state data for type_id `{:?}`",
|
||||
type_id
|
||||
);
|
||||
self
|
||||
.data
|
||||
.remove(&type_id)
|
||||
.and_then(|b| b.downcast().ok())
|
||||
.map(|b| *b)
|
||||
}
|
||||
|
||||
/// Moves a value out of the `GothamState` storage and returns ownership.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// If a value of type `T` is not present in `GothamState`.
|
||||
pub fn take<T: 'static>(&mut self) -> T {
|
||||
self.try_take().unwrap_or_else(|| missing::<T>())
|
||||
}
|
||||
}
|
||||
|
||||
fn missing<T: 'static>() -> ! {
|
||||
panic!(
|
||||
"required type {} is not present in GothamState container",
|
||||
type_name::<T>()
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::GothamState;
|
||||
|
||||
struct MyStruct {
|
||||
value: i32,
|
||||
}
|
||||
|
||||
struct AnotherStruct {
|
||||
value: &'static str,
|
||||
}
|
||||
|
||||
type Alias1 = String;
|
||||
type Alias2 = String;
|
||||
|
||||
#[test]
|
||||
fn put_borrow1() {
|
||||
let mut state = GothamState::default();
|
||||
state.put(MyStruct { value: 1 });
|
||||
assert_eq!(state.borrow::<MyStruct>().value, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn put_borrow2() {
|
||||
let mut state = GothamState::default();
|
||||
assert!(!state.has::<AnotherStruct>());
|
||||
state.put(AnotherStruct { value: "a string" });
|
||||
assert!(state.has::<AnotherStruct>());
|
||||
assert!(!state.has::<MyStruct>());
|
||||
state.put(MyStruct { value: 100 });
|
||||
assert!(state.has::<MyStruct>());
|
||||
assert_eq!(state.borrow::<MyStruct>().value, 100);
|
||||
assert_eq!(state.borrow::<AnotherStruct>().value, "a string");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn try_borrow() {
|
||||
let mut state = GothamState::default();
|
||||
state.put(MyStruct { value: 100 });
|
||||
assert!(state.try_borrow::<MyStruct>().is_some());
|
||||
assert_eq!(state.try_borrow::<MyStruct>().unwrap().value, 100);
|
||||
assert!(state.try_borrow::<AnotherStruct>().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn try_borrow_mut() {
|
||||
let mut state = GothamState::default();
|
||||
state.put(MyStruct { value: 100 });
|
||||
if let Some(a) = state.try_borrow_mut::<MyStruct>() {
|
||||
a.value += 10;
|
||||
}
|
||||
assert_eq!(state.borrow::<MyStruct>().value, 110);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn borrow_mut() {
|
||||
let mut state = GothamState::default();
|
||||
state.put(MyStruct { value: 100 });
|
||||
{
|
||||
let a = state.borrow_mut::<MyStruct>();
|
||||
a.value += 10;
|
||||
}
|
||||
assert_eq!(state.borrow::<MyStruct>().value, 110);
|
||||
assert!(state.try_borrow_mut::<AnotherStruct>().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn try_take() {
|
||||
let mut state = GothamState::default();
|
||||
state.put(MyStruct { value: 100 });
|
||||
assert_eq!(state.try_take::<MyStruct>().unwrap().value, 100);
|
||||
assert!(state.try_take::<MyStruct>().is_none());
|
||||
assert!(state.try_borrow_mut::<MyStruct>().is_none());
|
||||
assert!(state.try_borrow::<MyStruct>().is_none());
|
||||
assert!(state.try_take::<AnotherStruct>().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn take() {
|
||||
let mut state = GothamState::default();
|
||||
state.put(MyStruct { value: 110 });
|
||||
assert_eq!(state.take::<MyStruct>().value, 110);
|
||||
assert!(state.try_take::<MyStruct>().is_none());
|
||||
assert!(state.try_borrow_mut::<MyStruct>().is_none());
|
||||
assert!(state.try_borrow::<MyStruct>().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn type_alias() {
|
||||
let mut state = GothamState::default();
|
||||
state.put::<Alias1>("alias1".to_string());
|
||||
state.put::<Alias2>("alias2".to_string());
|
||||
assert_eq!(state.take::<Alias1>(), "alias2");
|
||||
assert!(state.try_take::<Alias1>().is_none());
|
||||
assert!(state.try_take::<Alias2>().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(
|
||||
expected = "required type deno_core::gotham_state::tests::MyStruct is not present in GothamState container"
|
||||
)]
|
||||
fn missing() {
|
||||
let state = GothamState::default();
|
||||
let _ = state.borrow::<MyStruct>();
|
||||
}
|
||||
}
|
|
@ -1,853 +0,0 @@
|
|||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
||||
|
||||
//! The documentation for the inspector API is sparse, but these are helpful:
|
||||
//! <https://chromedevtools.github.io/devtools-protocol/>
|
||||
//! <https://hyperandroid.com/2020/02/12/v8-inspector-from-an-embedder-standpoint/>
|
||||
|
||||
use crate::error::generic_error;
|
||||
use crate::futures::channel::mpsc;
|
||||
use crate::futures::channel::mpsc::UnboundedReceiver;
|
||||
use crate::futures::channel::mpsc::UnboundedSender;
|
||||
use crate::futures::channel::oneshot;
|
||||
use crate::futures::future::select;
|
||||
use crate::futures::future::Either;
|
||||
use crate::futures::prelude::*;
|
||||
use crate::futures::stream::SelectAll;
|
||||
use crate::futures::stream::StreamExt;
|
||||
use crate::futures::task;
|
||||
use crate::futures::task::Context;
|
||||
use crate::futures::task::Poll;
|
||||
use crate::serde_json;
|
||||
use crate::serde_json::json;
|
||||
use crate::serde_json::Value;
|
||||
use anyhow::Error;
|
||||
use parking_lot::Mutex;
|
||||
use std::cell::BorrowMutError;
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
use std::ffi::c_void;
|
||||
use std::mem::take;
|
||||
use std::mem::MaybeUninit;
|
||||
use std::pin::Pin;
|
||||
use std::ptr;
|
||||
use std::ptr::NonNull;
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
use std::thread;
|
||||
use v8::HandleScope;
|
||||
|
||||
pub enum InspectorMsgKind {
|
||||
Notification,
|
||||
Message(i32),
|
||||
}
|
||||
pub struct InspectorMsg {
|
||||
pub kind: InspectorMsgKind,
|
||||
pub content: String,
|
||||
}
|
||||
pub type SessionProxySender = UnboundedSender<InspectorMsg>;
|
||||
pub type SessionProxyReceiver = UnboundedReceiver<String>;
|
||||
|
||||
/// Encapsulates an UnboundedSender/UnboundedReceiver pair that together form
|
||||
/// a duplex channel for sending/receiving messages in V8 session.
|
||||
pub struct InspectorSessionProxy {
|
||||
pub tx: SessionProxySender,
|
||||
pub rx: SessionProxyReceiver,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
enum PollState {
|
||||
Idle,
|
||||
Woken,
|
||||
Polling,
|
||||
Parked,
|
||||
Dropped,
|
||||
}
|
||||
|
||||
/// This structure is used responsible for providing inspector interface
|
||||
/// to the `JsRuntime`.
|
||||
///
|
||||
/// It stores an instance of `v8::inspector::V8Inspector` and additionally
|
||||
/// implements `v8::inspector::V8InspectorClientImpl`.
|
||||
///
|
||||
/// After creating this structure it's possible to connect multiple sessions
|
||||
/// to the inspector, in case of Deno it's either: a "websocket session" that
|
||||
/// provides integration with Chrome Devtools, or an "in-memory session" that
|
||||
/// is used for REPL or coverage collection.
|
||||
pub struct JsRuntimeInspector {
|
||||
v8_inspector_client: v8::inspector::V8InspectorClientBase,
|
||||
v8_inspector: Rc<RefCell<v8::UniquePtr<v8::inspector::V8Inspector>>>,
|
||||
new_session_tx: UnboundedSender<InspectorSessionProxy>,
|
||||
sessions: RefCell<SessionContainer>,
|
||||
flags: RefCell<InspectorFlags>,
|
||||
waker: Arc<InspectorWaker>,
|
||||
deregister_tx: Option<oneshot::Sender<()>>,
|
||||
is_dispatching_message: RefCell<bool>,
|
||||
}
|
||||
|
||||
impl Drop for JsRuntimeInspector {
|
||||
fn drop(&mut self) {
|
||||
// Since the waker is cloneable, it might outlive the inspector itself.
|
||||
// Set the poll state to 'dropped' so it doesn't attempt to request an
|
||||
// interrupt from the isolate.
|
||||
self.waker.update(|w| w.poll_state = PollState::Dropped);
|
||||
|
||||
// V8 automatically deletes all sessions when an `V8Inspector` instance is
|
||||
// deleted, however InspectorSession also has a drop handler that cleans
|
||||
// up after itself. To avoid a double free, make sure the inspector is
|
||||
// dropped last.
|
||||
self.sessions.borrow_mut().drop_sessions();
|
||||
|
||||
// Notify counterparty that this instance is being destroyed. Ignoring
|
||||
// result because counterparty waiting for the signal might have already
|
||||
// dropped the other end of channel.
|
||||
if let Some(deregister_tx) = self.deregister_tx.take() {
|
||||
let _ = deregister_tx.send(());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl v8::inspector::V8InspectorClientImpl for JsRuntimeInspector {
|
||||
fn base(&self) -> &v8::inspector::V8InspectorClientBase {
|
||||
&self.v8_inspector_client
|
||||
}
|
||||
|
||||
unsafe fn base_ptr(
|
||||
this: *const Self,
|
||||
) -> *const v8::inspector::V8InspectorClientBase
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
// SAFETY: this pointer is valid for the whole lifetime of inspector
|
||||
unsafe { std::ptr::addr_of!((*this).v8_inspector_client) }
|
||||
}
|
||||
|
||||
fn base_mut(&mut self) -> &mut v8::inspector::V8InspectorClientBase {
|
||||
&mut self.v8_inspector_client
|
||||
}
|
||||
|
||||
fn run_message_loop_on_pause(&mut self, context_group_id: i32) {
|
||||
assert_eq!(context_group_id, JsRuntimeInspector::CONTEXT_GROUP_ID);
|
||||
self.flags.borrow_mut().on_pause = true;
|
||||
let _ = self.poll_sessions(None);
|
||||
}
|
||||
|
||||
fn quit_message_loop_on_pause(&mut self) {
|
||||
self.flags.borrow_mut().on_pause = false;
|
||||
}
|
||||
|
||||
fn run_if_waiting_for_debugger(&mut self, context_group_id: i32) {
|
||||
assert_eq!(context_group_id, JsRuntimeInspector::CONTEXT_GROUP_ID);
|
||||
self.flags.borrow_mut().waiting_for_session = false;
|
||||
}
|
||||
}
|
||||
|
||||
impl JsRuntimeInspector {
|
||||
/// Currently Deno supports only a single context in `JsRuntime`
|
||||
/// and thus it's id is provided as an associated constant.
|
||||
const CONTEXT_GROUP_ID: i32 = 1;
|
||||
|
||||
pub fn new(
|
||||
scope: &mut v8::HandleScope,
|
||||
context: v8::Local<v8::Context>,
|
||||
is_main: bool,
|
||||
) -> Rc<RefCell<Self>> {
|
||||
let (new_session_tx, new_session_rx) =
|
||||
mpsc::unbounded::<InspectorSessionProxy>();
|
||||
|
||||
let v8_inspector_client =
|
||||
v8::inspector::V8InspectorClientBase::new::<Self>();
|
||||
|
||||
let waker = InspectorWaker::new(scope.thread_safe_handle());
|
||||
|
||||
// Create JsRuntimeInspector instance.
|
||||
let self__ = Rc::new(RefCell::new(Self {
|
||||
v8_inspector_client,
|
||||
v8_inspector: Default::default(),
|
||||
sessions: RefCell::new(SessionContainer::temporary_placeholder()),
|
||||
new_session_tx,
|
||||
flags: Default::default(),
|
||||
waker,
|
||||
deregister_tx: None,
|
||||
is_dispatching_message: Default::default(),
|
||||
}));
|
||||
let mut self_ = self__.borrow_mut();
|
||||
self_.v8_inspector = Rc::new(RefCell::new(
|
||||
v8::inspector::V8Inspector::create(scope, &mut *self_).into(),
|
||||
));
|
||||
self_.sessions = RefCell::new(SessionContainer::new(
|
||||
self_.v8_inspector.clone(),
|
||||
new_session_rx,
|
||||
));
|
||||
|
||||
// Tell the inspector about the global context.
|
||||
let context_name = v8::inspector::StringView::from(&b"global context"[..]);
|
||||
// NOTE(bartlomieju): this is what Node.js does and it turns out some
|
||||
// debuggers (like VSCode) rely on this information to disconnect after
|
||||
// program completes
|
||||
let aux_data = if is_main {
|
||||
r#"{"isDefault": true}"#
|
||||
} else {
|
||||
r#"{"isDefault": false}"#
|
||||
};
|
||||
let aux_data_view = v8::inspector::StringView::from(aux_data.as_bytes());
|
||||
self_
|
||||
.v8_inspector
|
||||
.borrow_mut()
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.context_created(
|
||||
context,
|
||||
Self::CONTEXT_GROUP_ID,
|
||||
context_name,
|
||||
aux_data_view,
|
||||
);
|
||||
|
||||
// Poll the session handler so we will get notified whenever there is
|
||||
// new incoming debugger activity.
|
||||
let _ = self_.poll_sessions(None).unwrap();
|
||||
drop(self_);
|
||||
|
||||
self__
|
||||
}
|
||||
|
||||
pub fn is_dispatching_message(&self) -> bool {
|
||||
*self.is_dispatching_message.borrow()
|
||||
}
|
||||
|
||||
pub fn context_destroyed(
|
||||
&mut self,
|
||||
scope: &mut HandleScope,
|
||||
context: v8::Global<v8::Context>,
|
||||
) {
|
||||
let context = v8::Local::new(scope, context);
|
||||
self
|
||||
.v8_inspector
|
||||
.borrow_mut()
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.context_destroyed(context);
|
||||
}
|
||||
|
||||
pub fn exception_thrown(
|
||||
&self,
|
||||
scope: &mut HandleScope,
|
||||
exception: v8::Local<'_, v8::Value>,
|
||||
in_promise: bool,
|
||||
) {
|
||||
let context = scope.get_current_context();
|
||||
let message = v8::Exception::create_message(scope, exception);
|
||||
let stack_trace = message.get_stack_trace(scope).unwrap();
|
||||
let mut v8_inspector_ref = self.v8_inspector.borrow_mut();
|
||||
let v8_inspector = v8_inspector_ref.as_mut().unwrap();
|
||||
let stack_trace = v8_inspector.create_stack_trace(stack_trace);
|
||||
v8_inspector.exception_thrown(
|
||||
context,
|
||||
if in_promise {
|
||||
v8::inspector::StringView::from("Uncaught (in promise)".as_bytes())
|
||||
} else {
|
||||
v8::inspector::StringView::from("Uncaught".as_bytes())
|
||||
},
|
||||
exception,
|
||||
v8::inspector::StringView::from("".as_bytes()),
|
||||
v8::inspector::StringView::from("".as_bytes()),
|
||||
0,
|
||||
0,
|
||||
stack_trace,
|
||||
0,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn has_active_sessions(&self) -> bool {
|
||||
self.sessions.borrow().has_active_sessions()
|
||||
}
|
||||
|
||||
pub fn has_blocking_sessions(&self) -> bool {
|
||||
self.sessions.borrow().has_blocking_sessions()
|
||||
}
|
||||
|
||||
pub fn poll_sessions(
|
||||
&self,
|
||||
mut invoker_cx: Option<&mut Context>,
|
||||
) -> Result<Poll<()>, BorrowMutError> {
|
||||
// The futures this function uses do not have re-entrant poll() functions.
|
||||
// However it is can happen that poll_sessions() gets re-entered, e.g.
|
||||
// when an interrupt request is honored while the inspector future is polled
|
||||
// by the task executor. We let the caller know by returning some error.
|
||||
let mut sessions = self.sessions.try_borrow_mut()?;
|
||||
|
||||
self.waker.update(|w| {
|
||||
match w.poll_state {
|
||||
PollState::Idle | PollState::Woken => w.poll_state = PollState::Polling,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
});
|
||||
|
||||
// Create a new Context object that will make downstream futures
|
||||
// use the InspectorWaker when they are ready to be polled again.
|
||||
let waker_ref = task::waker_ref(&self.waker);
|
||||
let cx = &mut Context::from_waker(&waker_ref);
|
||||
|
||||
loop {
|
||||
loop {
|
||||
// Do one "handshake" with a newly connected session at a time.
|
||||
if let Some(mut session) = sessions.handshake.take() {
|
||||
let poll_result = session.poll_next_unpin(cx);
|
||||
match poll_result {
|
||||
Poll::Pending => {
|
||||
sessions.established.push(session);
|
||||
continue;
|
||||
}
|
||||
Poll::Ready(Some(session_stream_item)) => {
|
||||
let (v8_session_ptr, msg) = session_stream_item;
|
||||
InspectorSession::dispatch_message(v8_session_ptr, msg);
|
||||
sessions.established.push(session);
|
||||
continue;
|
||||
}
|
||||
Poll::Ready(None) => {}
|
||||
}
|
||||
}
|
||||
|
||||
// Accept new connections.
|
||||
let poll_result = sessions.session_rx.poll_next_unpin(cx);
|
||||
if let Poll::Ready(Some(session_proxy)) = poll_result {
|
||||
let session = InspectorSession::new(
|
||||
sessions.v8_inspector.clone(),
|
||||
session_proxy,
|
||||
false,
|
||||
);
|
||||
let prev = sessions.handshake.replace(session);
|
||||
assert!(prev.is_none());
|
||||
}
|
||||
|
||||
// Poll established sessions.
|
||||
match sessions.established.poll_next_unpin(cx) {
|
||||
Poll::Ready(Some(session_stream_item)) => {
|
||||
let (v8_session_ptr, msg) = session_stream_item;
|
||||
*self.is_dispatching_message.borrow_mut() = true;
|
||||
InspectorSession::dispatch_message(v8_session_ptr, msg);
|
||||
*self.is_dispatching_message.borrow_mut() = false;
|
||||
continue;
|
||||
}
|
||||
Poll::Ready(None) => break,
|
||||
Poll::Pending => break,
|
||||
};
|
||||
}
|
||||
|
||||
let should_block =
|
||||
self.flags.borrow().on_pause || self.flags.borrow().waiting_for_session;
|
||||
|
||||
let new_state = self.waker.update(|w| {
|
||||
match w.poll_state {
|
||||
PollState::Woken => {
|
||||
// The inspector was woken while the session handler was being
|
||||
// polled, so we poll it another time.
|
||||
w.poll_state = PollState::Polling;
|
||||
}
|
||||
PollState::Polling if !should_block => {
|
||||
// The session handler doesn't need to be polled any longer, and
|
||||
// there's no reason to block (execution is not paused), so this
|
||||
// function is about to return.
|
||||
w.poll_state = PollState::Idle;
|
||||
// Register the task waker that can be used to wake the parent
|
||||
// task that will poll the inspector future.
|
||||
if let Some(cx) = invoker_cx.take() {
|
||||
w.task_waker.replace(cx.waker().clone());
|
||||
}
|
||||
// Register the address of the inspector, which allows the waker
|
||||
// to request an interrupt from the isolate.
|
||||
w.inspector_ptr = NonNull::new(self as *const _ as *mut Self);
|
||||
}
|
||||
PollState::Polling if should_block => {
|
||||
// Isolate execution has been paused but there are no more
|
||||
// events to process, so this thread will be parked. Therefore,
|
||||
// store the current thread handle in the waker so it knows
|
||||
// which thread to unpark when new events arrive.
|
||||
w.poll_state = PollState::Parked;
|
||||
w.parked_thread.replace(thread::current());
|
||||
}
|
||||
_ => unreachable!(),
|
||||
};
|
||||
w.poll_state
|
||||
});
|
||||
match new_state {
|
||||
PollState::Idle => break Ok(Poll::Pending), // Yield to task.
|
||||
PollState::Polling => {} // Poll the session handler again.
|
||||
PollState::Parked => thread::park(), // Park the thread.
|
||||
_ => unreachable!(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// This function blocks the thread until at least one inspector client has
|
||||
/// established a websocket connection.
|
||||
pub fn wait_for_session(&mut self) {
|
||||
loop {
|
||||
match self.sessions.get_mut().established.iter_mut().next() {
|
||||
Some(_session) => {
|
||||
self.flags.get_mut().waiting_for_session = false;
|
||||
break;
|
||||
}
|
||||
None => {
|
||||
self.flags.get_mut().waiting_for_session = true;
|
||||
let _ = self.poll_sessions(None).unwrap();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// This function blocks the thread until at least one inspector client has
|
||||
/// established a websocket connection.
|
||||
///
|
||||
/// After that, it instructs V8 to pause at the next statement.
|
||||
/// Frontend must send "Runtime.runIfWaitingForDebugger" message to resume
|
||||
/// execution.
|
||||
pub fn wait_for_session_and_break_on_next_statement(&mut self) {
|
||||
loop {
|
||||
match self.sessions.get_mut().established.iter_mut().next() {
|
||||
Some(session) => break session.break_on_next_statement(),
|
||||
None => {
|
||||
self.flags.get_mut().waiting_for_session = true;
|
||||
let _ = self.poll_sessions(None).unwrap();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// Obtain a sender for proxy channels.
|
||||
pub fn get_session_sender(&self) -> UnboundedSender<InspectorSessionProxy> {
|
||||
self.new_session_tx.clone()
|
||||
}
|
||||
|
||||
/// Create a channel that notifies the frontend when inspector is dropped.
|
||||
///
|
||||
/// NOTE: Only a single handler is currently available.
|
||||
pub fn add_deregister_handler(&mut self) -> oneshot::Receiver<()> {
|
||||
let (tx, rx) = oneshot::channel::<()>();
|
||||
let prev = self.deregister_tx.replace(tx);
|
||||
assert!(
|
||||
prev.is_none(),
|
||||
"Only a single deregister handler is allowed"
|
||||
);
|
||||
rx
|
||||
}
|
||||
|
||||
/// Create a local inspector session that can be used on
|
||||
/// the same thread as the isolate.
|
||||
pub fn create_local_session(&self) -> LocalInspectorSession {
|
||||
// The 'outbound' channel carries messages sent to the session.
|
||||
let (outbound_tx, outbound_rx) = mpsc::unbounded();
|
||||
|
||||
// The 'inbound' channel carries messages received from the session.
|
||||
let (inbound_tx, inbound_rx) = mpsc::unbounded();
|
||||
|
||||
let proxy = InspectorSessionProxy {
|
||||
tx: outbound_tx,
|
||||
rx: inbound_rx,
|
||||
};
|
||||
|
||||
// InspectorSessions for a local session is added directly to the "established"
|
||||
// sessions, so it doesn't need to go through the session sender.
|
||||
let inspector_session =
|
||||
InspectorSession::new(self.v8_inspector.clone(), proxy, true);
|
||||
self
|
||||
.sessions
|
||||
.borrow_mut()
|
||||
.established
|
||||
.push(inspector_session);
|
||||
take(&mut self.flags.borrow_mut().waiting_for_session);
|
||||
|
||||
LocalInspectorSession::new(inbound_tx, outbound_rx)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct InspectorFlags {
|
||||
waiting_for_session: bool,
|
||||
on_pause: bool,
|
||||
}
|
||||
|
||||
/// A helper structure that helps coordinate sessions during different
|
||||
/// parts of their lifecycle.
|
||||
struct SessionContainer {
|
||||
v8_inspector: Rc<RefCell<v8::UniquePtr<v8::inspector::V8Inspector>>>,
|
||||
session_rx: UnboundedReceiver<InspectorSessionProxy>,
|
||||
handshake: Option<Box<InspectorSession>>,
|
||||
established: SelectAll<Box<InspectorSession>>,
|
||||
}
|
||||
|
||||
impl SessionContainer {
|
||||
fn new(
|
||||
v8_inspector: Rc<RefCell<v8::UniquePtr<v8::inspector::V8Inspector>>>,
|
||||
new_session_rx: UnboundedReceiver<InspectorSessionProxy>,
|
||||
) -> Self {
|
||||
Self {
|
||||
v8_inspector,
|
||||
session_rx: new_session_rx,
|
||||
handshake: None,
|
||||
established: SelectAll::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// V8 automatically deletes all sessions when an `V8Inspector` instance is
|
||||
/// deleted, however InspectorSession also has a drop handler that cleans
|
||||
/// up after itself. To avoid a double free, we need to manually drop
|
||||
/// all sessions before dropping the inspector instance.
|
||||
fn drop_sessions(&mut self) {
|
||||
self.v8_inspector = Default::default();
|
||||
self.handshake.take();
|
||||
self.established.clear();
|
||||
}
|
||||
|
||||
fn has_active_sessions(&self) -> bool {
|
||||
!self.established.is_empty() || self.handshake.is_some()
|
||||
}
|
||||
|
||||
fn has_blocking_sessions(&self) -> bool {
|
||||
self.established.iter().any(|s| s.blocking)
|
||||
}
|
||||
|
||||
/// A temporary placeholder that should be used before actual
|
||||
/// instance of V8Inspector is created. It's used in favor
|
||||
/// of `Default` implementation to signal that it's not meant
|
||||
/// for actual use.
|
||||
fn temporary_placeholder() -> Self {
|
||||
let (_tx, rx) = mpsc::unbounded::<InspectorSessionProxy>();
|
||||
Self {
|
||||
v8_inspector: Default::default(),
|
||||
session_rx: rx,
|
||||
handshake: None,
|
||||
established: SelectAll::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct InspectorWakerInner {
|
||||
poll_state: PollState,
|
||||
task_waker: Option<task::Waker>,
|
||||
parked_thread: Option<thread::Thread>,
|
||||
inspector_ptr: Option<NonNull<JsRuntimeInspector>>,
|
||||
isolate_handle: v8::IsolateHandle,
|
||||
}
|
||||
|
||||
// SAFETY: unsafe trait must have unsafe implementation
|
||||
unsafe impl Send for InspectorWakerInner {}
|
||||
|
||||
struct InspectorWaker(Mutex<InspectorWakerInner>);
|
||||
|
||||
impl InspectorWaker {
|
||||
fn new(isolate_handle: v8::IsolateHandle) -> Arc<Self> {
|
||||
let inner = InspectorWakerInner {
|
||||
poll_state: PollState::Idle,
|
||||
task_waker: None,
|
||||
parked_thread: None,
|
||||
inspector_ptr: None,
|
||||
isolate_handle,
|
||||
};
|
||||
Arc::new(Self(Mutex::new(inner)))
|
||||
}
|
||||
|
||||
fn update<F, R>(&self, update_fn: F) -> R
|
||||
where
|
||||
F: FnOnce(&mut InspectorWakerInner) -> R,
|
||||
{
|
||||
let mut g = self.0.lock();
|
||||
update_fn(&mut g)
|
||||
}
|
||||
}
|
||||
|
||||
impl task::ArcWake for InspectorWaker {
|
||||
fn wake_by_ref(arc_self: &Arc<Self>) {
|
||||
arc_self.update(|w| {
|
||||
match w.poll_state {
|
||||
PollState::Idle => {
|
||||
// Wake the task, if any, that has polled the Inspector future last.
|
||||
if let Some(waker) = w.task_waker.take() {
|
||||
waker.wake()
|
||||
}
|
||||
// Request an interrupt from the isolate if it's running and there's
|
||||
// not unhandled interrupt request in flight.
|
||||
if let Some(arg) = w
|
||||
.inspector_ptr
|
||||
.take()
|
||||
.map(|ptr| ptr.as_ptr() as *mut c_void)
|
||||
{
|
||||
w.isolate_handle.request_interrupt(handle_interrupt, arg);
|
||||
}
|
||||
extern "C" fn handle_interrupt(
|
||||
_isolate: &mut v8::Isolate,
|
||||
arg: *mut c_void,
|
||||
) {
|
||||
// SAFETY: `InspectorWaker` is owned by `JsRuntimeInspector`, so the
|
||||
// pointer to the latter is valid as long as waker is alive.
|
||||
let inspector = unsafe { &*(arg as *mut JsRuntimeInspector) };
|
||||
let _ = inspector.poll_sessions(None);
|
||||
}
|
||||
}
|
||||
PollState::Parked => {
|
||||
// Unpark the isolate thread.
|
||||
let parked_thread = w.parked_thread.take().unwrap();
|
||||
assert_ne!(parked_thread.id(), thread::current().id());
|
||||
parked_thread.unpark();
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
w.poll_state = PollState::Woken;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// An inspector session that proxies messages to concrete "transport layer",
|
||||
/// eg. Websocket or another set of channels.
|
||||
struct InspectorSession {
|
||||
v8_channel: v8::inspector::ChannelBase,
|
||||
v8_session: v8::UniqueRef<v8::inspector::V8InspectorSession>,
|
||||
proxy: InspectorSessionProxy,
|
||||
// Describes if session should keep event loop alive, eg. a local REPL
|
||||
// session should keep event loop alive, but a Websocket session shouldn't.
|
||||
blocking: bool,
|
||||
}
|
||||
|
||||
impl InspectorSession {
|
||||
const CONTEXT_GROUP_ID: i32 = 1;
|
||||
|
||||
pub fn new(
|
||||
v8_inspector_rc: Rc<RefCell<v8::UniquePtr<v8::inspector::V8Inspector>>>,
|
||||
session_proxy: InspectorSessionProxy,
|
||||
blocking: bool,
|
||||
) -> Box<Self> {
|
||||
new_box_with(move |self_ptr| {
|
||||
let v8_channel = v8::inspector::ChannelBase::new::<Self>();
|
||||
let mut v8_inspector = v8_inspector_rc.borrow_mut();
|
||||
let v8_inspector_ptr = v8_inspector.as_mut().unwrap();
|
||||
// TODO(piscisaureus): safety comment
|
||||
#[allow(clippy::undocumented_unsafe_blocks)]
|
||||
let v8_session = v8_inspector_ptr.connect(
|
||||
Self::CONTEXT_GROUP_ID,
|
||||
// Todo(piscisaureus): V8Inspector::connect() should require that
|
||||
// the 'v8_channel' argument cannot move.
|
||||
unsafe { &mut *self_ptr },
|
||||
v8::inspector::StringView::empty(),
|
||||
v8::inspector::V8InspectorClientTrustLevel::FullyTrusted,
|
||||
);
|
||||
|
||||
Self {
|
||||
v8_channel,
|
||||
v8_session,
|
||||
proxy: session_proxy,
|
||||
blocking,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Dispatch message to V8 session
|
||||
fn dispatch_message(
|
||||
v8_session_ptr: *mut v8::inspector::V8InspectorSession,
|
||||
msg: String,
|
||||
) {
|
||||
let msg = v8::inspector::StringView::from(msg.as_bytes());
|
||||
// SAFETY: `InspectorSession` is the only owner of `v8_session_ptr`, so
|
||||
// the pointer is valid for as long the struct.
|
||||
unsafe {
|
||||
(*v8_session_ptr).dispatch_protocol_message(msg);
|
||||
};
|
||||
}
|
||||
|
||||
fn send_message(
|
||||
&self,
|
||||
msg_kind: InspectorMsgKind,
|
||||
msg: v8::UniquePtr<v8::inspector::StringBuffer>,
|
||||
) {
|
||||
let msg = msg.unwrap().string().to_string();
|
||||
let _ = self.proxy.tx.unbounded_send(InspectorMsg {
|
||||
kind: msg_kind,
|
||||
content: msg,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn break_on_next_statement(&mut self) {
|
||||
let reason = v8::inspector::StringView::from(&b"debugCommand"[..]);
|
||||
let detail = v8::inspector::StringView::empty();
|
||||
// TODO(bartlomieju): use raw `*mut V8InspectorSession` pointer, as this
|
||||
// reference may become aliased.
|
||||
(*self.v8_session).schedule_pause_on_next_statement(reason, detail);
|
||||
}
|
||||
}
|
||||
|
||||
impl v8::inspector::ChannelImpl for InspectorSession {
|
||||
fn base(&self) -> &v8::inspector::ChannelBase {
|
||||
&self.v8_channel
|
||||
}
|
||||
|
||||
unsafe fn base_ptr(this: *const Self) -> *const v8::inspector::ChannelBase
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
// SAFETY: this pointer is valid for the whole lifetime of inspector
|
||||
unsafe { std::ptr::addr_of!((*this).v8_channel) }
|
||||
}
|
||||
|
||||
fn base_mut(&mut self) -> &mut v8::inspector::ChannelBase {
|
||||
&mut self.v8_channel
|
||||
}
|
||||
|
||||
fn send_response(
|
||||
&mut self,
|
||||
call_id: i32,
|
||||
message: v8::UniquePtr<v8::inspector::StringBuffer>,
|
||||
) {
|
||||
self.send_message(InspectorMsgKind::Message(call_id), message);
|
||||
}
|
||||
|
||||
fn send_notification(
|
||||
&mut self,
|
||||
message: v8::UniquePtr<v8::inspector::StringBuffer>,
|
||||
) {
|
||||
self.send_message(InspectorMsgKind::Notification, message);
|
||||
}
|
||||
|
||||
fn flush_protocol_notifications(&mut self) {}
|
||||
}
|
||||
|
||||
impl Stream for InspectorSession {
|
||||
type Item = (*mut v8::inspector::V8InspectorSession, String);
|
||||
|
||||
fn poll_next(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context,
|
||||
) -> Poll<Option<Self::Item>> {
|
||||
let inner = self.get_mut();
|
||||
if let Poll::Ready(maybe_msg) = inner.proxy.rx.poll_next_unpin(cx) {
|
||||
if let Some(msg) = maybe_msg {
|
||||
return Poll::Ready(Some((&mut *inner.v8_session, msg)));
|
||||
} else {
|
||||
return Poll::Ready(None);
|
||||
}
|
||||
}
|
||||
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
|
||||
/// A local inspector session that can be used to send and receive protocol messages directly on
|
||||
/// the same thread as an isolate.
|
||||
pub struct LocalInspectorSession {
|
||||
v8_session_tx: UnboundedSender<String>,
|
||||
v8_session_rx: UnboundedReceiver<InspectorMsg>,
|
||||
response_tx_map: HashMap<i32, oneshot::Sender<serde_json::Value>>,
|
||||
next_message_id: i32,
|
||||
notification_tx: UnboundedSender<Value>,
|
||||
notification_rx: Option<UnboundedReceiver<Value>>,
|
||||
}
|
||||
|
||||
impl LocalInspectorSession {
|
||||
pub fn new(
|
||||
v8_session_tx: UnboundedSender<String>,
|
||||
v8_session_rx: UnboundedReceiver<InspectorMsg>,
|
||||
) -> Self {
|
||||
let response_tx_map = HashMap::new();
|
||||
let next_message_id = 0;
|
||||
|
||||
let (notification_tx, notification_rx) = mpsc::unbounded::<Value>();
|
||||
|
||||
Self {
|
||||
v8_session_tx,
|
||||
v8_session_rx,
|
||||
response_tx_map,
|
||||
next_message_id,
|
||||
notification_tx,
|
||||
notification_rx: Some(notification_rx),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn take_notification_rx(&mut self) -> UnboundedReceiver<Value> {
|
||||
self.notification_rx.take().unwrap()
|
||||
}
|
||||
|
||||
pub async fn post_message<T: serde::Serialize>(
|
||||
&mut self,
|
||||
method: &str,
|
||||
params: Option<T>,
|
||||
) -> Result<serde_json::Value, Error> {
|
||||
let id = self.next_message_id;
|
||||
self.next_message_id += 1;
|
||||
|
||||
let (response_tx, mut response_rx) =
|
||||
oneshot::channel::<serde_json::Value>();
|
||||
self.response_tx_map.insert(id, response_tx);
|
||||
|
||||
let message = json!({
|
||||
"id": id,
|
||||
"method": method,
|
||||
"params": params,
|
||||
});
|
||||
|
||||
let stringified_msg = serde_json::to_string(&message).unwrap();
|
||||
self.v8_session_tx.unbounded_send(stringified_msg).unwrap();
|
||||
|
||||
loop {
|
||||
let receive_fut = self.receive_from_v8_session().boxed_local();
|
||||
match select(receive_fut, &mut response_rx).await {
|
||||
Either::Left(_) => continue,
|
||||
Either::Right((result, _)) => {
|
||||
let response = result?;
|
||||
if let Some(error) = response.get("error") {
|
||||
return Err(generic_error(error.to_string()));
|
||||
}
|
||||
|
||||
let result = response.get("result").unwrap().clone();
|
||||
return Ok(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn receive_from_v8_session(&mut self) {
|
||||
let inspector_msg = self.v8_session_rx.next().await.unwrap();
|
||||
if let InspectorMsgKind::Message(msg_id) = inspector_msg.kind {
|
||||
let message: serde_json::Value =
|
||||
match serde_json::from_str(&inspector_msg.content) {
|
||||
Ok(v) => v,
|
||||
Err(error) => match error.classify() {
|
||||
serde_json::error::Category::Syntax => json!({
|
||||
"id": msg_id,
|
||||
"result": {
|
||||
"result": {
|
||||
"type": "error",
|
||||
"description": "Unterminated string literal",
|
||||
"value": "Unterminated string literal",
|
||||
},
|
||||
"exceptionDetails": {
|
||||
"exceptionId": 0,
|
||||
"text": "Unterminated string literal",
|
||||
"lineNumber": 0,
|
||||
"columnNumber": 0
|
||||
},
|
||||
},
|
||||
}),
|
||||
_ => panic!("Could not parse inspector message"),
|
||||
},
|
||||
};
|
||||
|
||||
self
|
||||
.response_tx_map
|
||||
.remove(&msg_id)
|
||||
.unwrap()
|
||||
.send(message)
|
||||
.unwrap();
|
||||
} else {
|
||||
let message = serde_json::from_str(&inspector_msg.content).unwrap();
|
||||
// Ignore if the receiver has been dropped.
|
||||
let _ = self.notification_tx.unbounded_send(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn new_box_with<T>(new_fn: impl FnOnce(*mut T) -> T) -> Box<T> {
|
||||
let b = Box::new(MaybeUninit::<T>::uninit());
|
||||
let p = Box::into_raw(b) as *mut T;
|
||||
// SAFETY: memory layout for `T` is ensured on first line of this function
|
||||
unsafe {
|
||||
ptr::write(p, new_fn(p));
|
||||
Box::from_raw(p)
|
||||
}
|
||||
}
|
1079
core/internal.d.ts
vendored
1079
core/internal.d.ts
vendored
File diff suppressed because it is too large
Load diff
300
core/io.rs
300
core/io.rs
|
@ -1,300 +0,0 @@
|
|||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
||||
|
||||
use std::ops::Deref;
|
||||
use std::ops::DerefMut;
|
||||
|
||||
use bytes::Buf;
|
||||
use serde_v8::JsBuffer;
|
||||
|
||||
/// BufView is a wrapper around an underlying contiguous chunk of bytes. It can
|
||||
/// be created from a [JsBuffer], [bytes::Bytes], or [Vec<u8>] and implements
|
||||
/// `Deref<[u8]>` and `AsRef<[u8]>`.
|
||||
///
|
||||
/// The wrapper has the ability to constrain the exposed view to a sub-region of
|
||||
/// the underlying buffer. This is useful for write operations, because they may
|
||||
/// have to be called multiple times, with different views onto the buffer to be
|
||||
/// able to write it entirely.
|
||||
pub struct BufView {
|
||||
inner: BufViewInner,
|
||||
cursor: usize,
|
||||
}
|
||||
|
||||
enum BufViewInner {
|
||||
Empty,
|
||||
Bytes(bytes::Bytes),
|
||||
JsBuffer(JsBuffer),
|
||||
Vec(Vec<u8>),
|
||||
}
|
||||
|
||||
impl BufView {
|
||||
const fn from_inner(inner: BufViewInner) -> Self {
|
||||
Self { inner, cursor: 0 }
|
||||
}
|
||||
|
||||
pub const fn empty() -> Self {
|
||||
Self::from_inner(BufViewInner::Empty)
|
||||
}
|
||||
|
||||
/// Get the length of the buffer view. This is the length of the underlying
|
||||
/// buffer minus the cursor position.
|
||||
pub fn len(&self) -> usize {
|
||||
match &self.inner {
|
||||
BufViewInner::Empty => 0,
|
||||
BufViewInner::Bytes(bytes) => bytes.len() - self.cursor,
|
||||
BufViewInner::JsBuffer(js_buf) => js_buf.len() - self.cursor,
|
||||
BufViewInner::Vec(vec) => vec.len() - self.cursor,
|
||||
}
|
||||
}
|
||||
|
||||
/// Is the buffer view empty?
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.len() == 0
|
||||
}
|
||||
|
||||
/// Advance the internal cursor of the buffer view by `n` bytes.
|
||||
pub fn advance_cursor(&mut self, n: usize) {
|
||||
assert!(self.len() >= n);
|
||||
self.cursor += n;
|
||||
}
|
||||
|
||||
/// Reset the internal cursor of the buffer view to the beginning of the
|
||||
/// buffer. Returns the old cursor position.
|
||||
pub fn reset_cursor(&mut self) -> usize {
|
||||
let old = self.cursor;
|
||||
self.cursor = 0;
|
||||
old
|
||||
}
|
||||
}
|
||||
|
||||
impl Buf for BufView {
|
||||
fn remaining(&self) -> usize {
|
||||
self.len()
|
||||
}
|
||||
|
||||
fn chunk(&self) -> &[u8] {
|
||||
self.deref()
|
||||
}
|
||||
|
||||
fn advance(&mut self, cnt: usize) {
|
||||
self.advance_cursor(cnt)
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for BufView {
|
||||
type Target = [u8];
|
||||
|
||||
fn deref(&self) -> &[u8] {
|
||||
let buf = match &self.inner {
|
||||
BufViewInner::Empty => &[],
|
||||
BufViewInner::Bytes(bytes) => bytes.deref(),
|
||||
BufViewInner::JsBuffer(js_buf) => js_buf.deref(),
|
||||
BufViewInner::Vec(vec) => vec.deref(),
|
||||
};
|
||||
&buf[self.cursor..]
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<[u8]> for BufView {
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
self.deref()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<JsBuffer> for BufView {
|
||||
fn from(buf: JsBuffer) -> Self {
|
||||
Self::from_inner(BufViewInner::JsBuffer(buf))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<u8>> for BufView {
|
||||
fn from(vec: Vec<u8>) -> Self {
|
||||
Self::from_inner(BufViewInner::Vec(vec))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<bytes::Bytes> for BufView {
|
||||
fn from(buf: bytes::Bytes) -> Self {
|
||||
Self::from_inner(BufViewInner::Bytes(buf))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<BufView> for bytes::Bytes {
|
||||
fn from(buf: BufView) -> Self {
|
||||
match buf.inner {
|
||||
BufViewInner::Empty => bytes::Bytes::new(),
|
||||
BufViewInner::Bytes(bytes) => bytes,
|
||||
BufViewInner::JsBuffer(js_buf) => js_buf.into(),
|
||||
BufViewInner::Vec(vec) => vec.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// BufMutView is a wrapper around an underlying contiguous chunk of writable
|
||||
/// bytes. It can be created from a `JsBuffer` or a `Vec<u8>` and implements
|
||||
/// `DerefMut<[u8]>` and `AsMut<[u8]>`.
|
||||
///
|
||||
/// The wrapper has the ability to constrain the exposed view to a sub-region of
|
||||
/// the underlying buffer. This is useful for write operations, because they may
|
||||
/// have to be called multiple times, with different views onto the buffer to be
|
||||
/// able to write it entirely.
|
||||
///
|
||||
/// A `BufMutView` can be turned into a `BufView` by calling `BufMutView::into_view`.
|
||||
pub struct BufMutView {
|
||||
inner: BufMutViewInner,
|
||||
cursor: usize,
|
||||
}
|
||||
|
||||
enum BufMutViewInner {
|
||||
JsBuffer(JsBuffer),
|
||||
Vec(Vec<u8>),
|
||||
}
|
||||
|
||||
impl BufMutView {
|
||||
fn from_inner(inner: BufMutViewInner) -> Self {
|
||||
Self { inner, cursor: 0 }
|
||||
}
|
||||
|
||||
pub fn new(len: usize) -> Self {
|
||||
Self::from_inner(BufMutViewInner::Vec(vec![0; len]))
|
||||
}
|
||||
|
||||
/// Get the length of the buffer view. This is the length of the underlying
|
||||
/// buffer minus the cursor position.
|
||||
pub fn len(&self) -> usize {
|
||||
match &self.inner {
|
||||
BufMutViewInner::JsBuffer(js_buf) => js_buf.len() - self.cursor,
|
||||
BufMutViewInner::Vec(vec) => vec.len() - self.cursor,
|
||||
}
|
||||
}
|
||||
|
||||
/// Is the buffer view empty?
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.len() == 0
|
||||
}
|
||||
|
||||
/// Advance the internal cursor of the buffer view by `n` bytes.
|
||||
pub fn advance_cursor(&mut self, n: usize) {
|
||||
assert!(self.len() >= n);
|
||||
self.cursor += n;
|
||||
}
|
||||
|
||||
/// Reset the internal cursor of the buffer view to the beginning of the
|
||||
/// buffer. Returns the old cursor position.
|
||||
pub fn reset_cursor(&mut self) -> usize {
|
||||
let old = self.cursor;
|
||||
self.cursor = 0;
|
||||
old
|
||||
}
|
||||
|
||||
/// Turn this `BufMutView` into a `BufView`.
|
||||
pub fn into_view(self) -> BufView {
|
||||
let inner = match self.inner {
|
||||
BufMutViewInner::JsBuffer(js_buf) => BufViewInner::JsBuffer(js_buf),
|
||||
BufMutViewInner::Vec(vec) => BufViewInner::Vec(vec),
|
||||
};
|
||||
BufView {
|
||||
inner,
|
||||
cursor: self.cursor,
|
||||
}
|
||||
}
|
||||
|
||||
/// Unwrap the underlying buffer into a `Vec<u8>`, consuming the `BufMutView`.
|
||||
///
|
||||
/// This method panics when called on a `BufMutView` that was created from a
|
||||
/// `JsBuffer`.
|
||||
pub fn unwrap_vec(self) -> Vec<u8> {
|
||||
match self.inner {
|
||||
BufMutViewInner::JsBuffer(_) => {
|
||||
panic!("Cannot unwrap a JsBuffer backed BufMutView into a Vec");
|
||||
}
|
||||
BufMutViewInner::Vec(vec) => vec,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a mutable reference to an underlying `Vec<u8>`.
|
||||
///
|
||||
/// This method panics when called on a `BufMutView` that was created from a
|
||||
/// `JsBuffer`.
|
||||
pub fn get_mut_vec(&mut self) -> &mut Vec<u8> {
|
||||
match &mut self.inner {
|
||||
BufMutViewInner::JsBuffer(_) => {
|
||||
panic!("Cannot unwrap a JsBuffer backed BufMutView into a Vec");
|
||||
}
|
||||
BufMutViewInner::Vec(vec) => vec,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Buf for BufMutView {
|
||||
fn remaining(&self) -> usize {
|
||||
self.len()
|
||||
}
|
||||
|
||||
fn chunk(&self) -> &[u8] {
|
||||
self.deref()
|
||||
}
|
||||
|
||||
fn advance(&mut self, cnt: usize) {
|
||||
self.advance_cursor(cnt)
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for BufMutView {
|
||||
type Target = [u8];
|
||||
|
||||
fn deref(&self) -> &[u8] {
|
||||
let buf = match &self.inner {
|
||||
BufMutViewInner::JsBuffer(js_buf) => js_buf.deref(),
|
||||
BufMutViewInner::Vec(vec) => vec.deref(),
|
||||
};
|
||||
&buf[self.cursor..]
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for BufMutView {
|
||||
fn deref_mut(&mut self) -> &mut [u8] {
|
||||
let buf = match &mut self.inner {
|
||||
BufMutViewInner::JsBuffer(js_buf) => js_buf.deref_mut(),
|
||||
BufMutViewInner::Vec(vec) => vec.deref_mut(),
|
||||
};
|
||||
&mut buf[self.cursor..]
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<[u8]> for BufMutView {
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
self.deref()
|
||||
}
|
||||
}
|
||||
|
||||
impl AsMut<[u8]> for BufMutView {
|
||||
fn as_mut(&mut self) -> &mut [u8] {
|
||||
self.deref_mut()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<JsBuffer> for BufMutView {
|
||||
fn from(buf: JsBuffer) -> Self {
|
||||
Self::from_inner(BufMutViewInner::JsBuffer(buf))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<u8>> for BufMutView {
|
||||
fn from(buf: Vec<u8>) -> Self {
|
||||
Self::from_inner(BufMutViewInner::Vec(buf))
|
||||
}
|
||||
}
|
||||
|
||||
pub enum WriteOutcome {
|
||||
Partial { nwritten: usize, view: BufView },
|
||||
Full { nwritten: usize },
|
||||
}
|
||||
|
||||
impl WriteOutcome {
|
||||
pub fn nwritten(&self) -> usize {
|
||||
match self {
|
||||
WriteOutcome::Partial { nwritten, .. } => *nwritten,
|
||||
WriteOutcome::Full { nwritten } => *nwritten,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,92 +0,0 @@
|
|||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
||||
// Some code and comments under MIT license where adapted from Tokio code
|
||||
// Copyright (c) 2023 Tokio Contributors
|
||||
|
||||
use std::task::Context;
|
||||
use std::task::Poll;
|
||||
use std::task::Waker;
|
||||
|
||||
use futures::Future;
|
||||
use tokio::task::AbortHandle;
|
||||
use tokio::task::JoinError;
|
||||
|
||||
use crate::task::MaskFutureAsSend;
|
||||
use crate::task::MaskResultAsSend;
|
||||
|
||||
/// Wraps the tokio [`JoinSet`] to make it !Send-friendly and to make it easier and safer for us to
|
||||
/// poll while empty.
|
||||
pub(crate) struct JoinSet<T> {
|
||||
joinset: tokio::task::JoinSet<MaskResultAsSend<T>>,
|
||||
/// If join_next returns Ready(None), we stash the waker
|
||||
waker: Option<Waker>,
|
||||
}
|
||||
|
||||
impl<T> Default for JoinSet<T> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
joinset: Default::default(),
|
||||
waker: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: 'static> JoinSet<T> {
|
||||
/// Spawn the provided task on the `JoinSet`, returning an [`AbortHandle`]
|
||||
/// that can be used to remotely cancel the task.
|
||||
///
|
||||
/// The provided future will start running in the background immediately
|
||||
/// when this method is called, even if you don't await anything on this
|
||||
/// `JoinSet`.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This method panics if called outside of a Tokio runtime.
|
||||
///
|
||||
/// [`AbortHandle`]: tokio::task::AbortHandle
|
||||
#[track_caller]
|
||||
pub fn spawn<F>(&mut self, task: F) -> AbortHandle
|
||||
where
|
||||
F: Future<Output = T>,
|
||||
F: 'static,
|
||||
T: 'static,
|
||||
{
|
||||
// SAFETY: We only use this with the single-thread executor
|
||||
let handle = self.joinset.spawn(unsafe { MaskFutureAsSend::new(task) });
|
||||
|
||||
// If someone had called poll_join_next while we were empty, ask them to poll again
|
||||
// so we can properly register the waker with the underlying JoinSet.
|
||||
if let Some(waker) = self.waker.take() {
|
||||
waker.wake();
|
||||
}
|
||||
handle
|
||||
}
|
||||
|
||||
/// Returns the number of tasks currently in the `JoinSet`.
|
||||
pub fn len(&self) -> usize {
|
||||
self.joinset.len()
|
||||
}
|
||||
|
||||
/// Waits until one of the tasks in the set completes and returns its output.
|
||||
///
|
||||
/// # Cancel Safety
|
||||
///
|
||||
/// This method is cancel safe. If `join_next` is used as the event in a `tokio::select!`
|
||||
/// statement and some other branch completes first, it is guaranteed that no tasks were
|
||||
/// removed from this `JoinSet`.
|
||||
pub fn poll_join_next(
|
||||
&mut self,
|
||||
cx: &mut Context,
|
||||
) -> Poll<Result<T, JoinError>> {
|
||||
// TODO(mmastrac): Use poll_join_next from Tokio
|
||||
let next = std::pin::pin!(self.joinset.join_next());
|
||||
match next.poll(cx) {
|
||||
Poll::Ready(Some(res)) => Poll::Ready(res.map(|res| res.into_inner())),
|
||||
Poll::Ready(None) => {
|
||||
// Stash waker
|
||||
self.waker = Some(cx.waker().clone());
|
||||
Poll::Pending
|
||||
}
|
||||
Poll::Pending => Poll::Pending,
|
||||
}
|
||||
}
|
||||
}
|
212
core/lib.deno_core.d.ts
vendored
212
core/lib.deno_core.d.ts
vendored
|
@ -1,212 +0,0 @@
|
|||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
||||
|
||||
// deno-lint-ignore-file no-explicit-any
|
||||
|
||||
/// <reference no-default-lib="true" />
|
||||
/// <reference lib="esnext" />
|
||||
|
||||
declare namespace Deno {
|
||||
namespace core {
|
||||
/** Call an op in Rust, and asynchronously receive the result. */
|
||||
function opAsync(
|
||||
opName: string,
|
||||
...args: any[]
|
||||
): Promise<any>;
|
||||
|
||||
/** Mark following promise as "ref", ie. event loop won't exit
|
||||
* until all "ref" promises are resolved. All async ops are "ref" by default. */
|
||||
function refOp(promiseId: number): void;
|
||||
|
||||
/** Mark following promise as "unref", ie. event loop will exit
|
||||
* if there are only "unref" promises left. */
|
||||
function unrefOp(promiseId: number): void;
|
||||
|
||||
/**
|
||||
* List of all registered ops, in the form of a map that maps op
|
||||
* name to function.
|
||||
*/
|
||||
const ops: Record<string, (...args: unknown[]) => any>;
|
||||
|
||||
/**
|
||||
* List of all registered async ops, in the form of a map that maps op
|
||||
* name to function.
|
||||
*/
|
||||
const asyncOps: Record<string, (...args: unknown[]) => any>;
|
||||
|
||||
/**
|
||||
* Retrieve a list of all open resources, in the form of a map that maps
|
||||
* resource id to the resource name.
|
||||
*/
|
||||
function resources(): Record<string, string>;
|
||||
|
||||
/**
|
||||
* Close the resource with the specified op id. Throws `BadResource` error
|
||||
* if resource doesn't exist in resource table.
|
||||
*/
|
||||
function close(rid: number): void;
|
||||
|
||||
/**
|
||||
* Try close the resource with the specified op id; if resource with given
|
||||
* id doesn't exist do nothing.
|
||||
*/
|
||||
function tryClose(rid: number): void;
|
||||
|
||||
/**
|
||||
* Read from a (stream) resource that implements read()
|
||||
*/
|
||||
function read(rid: number, buf: Uint8Array): Promise<number>;
|
||||
|
||||
/**
|
||||
* Write to a (stream) resource that implements write()
|
||||
*/
|
||||
function write(rid: number, buf: Uint8Array): Promise<number>;
|
||||
|
||||
/**
|
||||
* Write to a (stream) resource that implements write()
|
||||
*/
|
||||
function writeAll(rid: number, buf: Uint8Array): Promise<void>;
|
||||
|
||||
/**
|
||||
* Synchronously read from a (stream) resource that implements readSync().
|
||||
*/
|
||||
function readSync(rid: number, buf: Uint8Array): number;
|
||||
|
||||
/**
|
||||
* Synchronously write to a (stream) resource that implements writeSync().
|
||||
*/
|
||||
function writeSync(rid: number, buf: Uint8Array): number;
|
||||
|
||||
/**
|
||||
* Print a message to stdout or stderr
|
||||
*/
|
||||
function print(message: string, is_err?: boolean): void;
|
||||
|
||||
/**
|
||||
* Shutdown a resource
|
||||
*/
|
||||
function shutdown(rid: number): Promise<void>;
|
||||
|
||||
/** Encode a string to its Uint8Array representation. */
|
||||
function encode(input: string): Uint8Array;
|
||||
|
||||
/**
|
||||
* Set a callback that will be called when the WebAssembly streaming APIs
|
||||
* (`WebAssembly.compileStreaming` and `WebAssembly.instantiateStreaming`)
|
||||
* are called in order to feed the source's bytes to the wasm compiler.
|
||||
* The callback is called with the source argument passed to the streaming
|
||||
* APIs and an rid to use with the wasm streaming ops.
|
||||
*
|
||||
* The callback should eventually invoke the following ops:
|
||||
* - `op_wasm_streaming_feed`. Feeds bytes from the wasm resource to the
|
||||
* compiler. Takes the rid and a `Uint8Array`.
|
||||
* - `op_wasm_streaming_abort`. Aborts the wasm compilation. Takes the rid
|
||||
* and an exception. Invalidates the resource.
|
||||
* - `op_wasm_streaming_set_url`. Sets a source URL for the wasm module.
|
||||
* Takes the rid and a string.
|
||||
* - To indicate the end of the resource, use `Deno.core.close()` with the
|
||||
* rid.
|
||||
*/
|
||||
function setWasmStreamingCallback(
|
||||
cb: (source: any, rid: number) => void,
|
||||
): void;
|
||||
|
||||
/**
|
||||
* Set a callback that will be called after resolving ops and before resolving
|
||||
* macrotasks.
|
||||
*/
|
||||
function setNextTickCallback(
|
||||
cb: () => void,
|
||||
): void;
|
||||
|
||||
/** Check if there's a scheduled "next tick". */
|
||||
function hasNextTickScheduled(): boolean;
|
||||
|
||||
/** Set a value telling the runtime if there are "next ticks" scheduled */
|
||||
function setHasNextTickScheduled(value: boolean): void;
|
||||
|
||||
/**
|
||||
* Set a callback that will be called after resolving ops and "next ticks".
|
||||
*/
|
||||
function setMacrotaskCallback(
|
||||
cb: () => boolean,
|
||||
): void;
|
||||
|
||||
/**
|
||||
* Set a callback that will be called when a promise without a .catch
|
||||
* handler is rejected. Returns the old handler or undefined.
|
||||
*/
|
||||
function setPromiseRejectCallback(
|
||||
cb: PromiseRejectCallback,
|
||||
): undefined | PromiseRejectCallback;
|
||||
|
||||
export type PromiseRejectCallback = (
|
||||
type: number,
|
||||
promise: Promise<unknown>,
|
||||
reason: any,
|
||||
) => void;
|
||||
|
||||
/**
|
||||
* Set a callback that will be called when an exception isn't caught
|
||||
* by any try/catch handlers. Currently only invoked when the callback
|
||||
* to setPromiseRejectCallback() throws an exception but that is expected
|
||||
* to change in the future. Returns the old handler or undefined.
|
||||
*/
|
||||
function setUncaughtExceptionCallback(
|
||||
cb: UncaughtExceptionCallback,
|
||||
): undefined | UncaughtExceptionCallback;
|
||||
|
||||
export type UncaughtExceptionCallback = (err: any) => void;
|
||||
|
||||
/**
|
||||
* Enables collection of stack traces of all async ops. This allows for
|
||||
* debugging of where a given async op was started. Deno CLI uses this for
|
||||
* improving error message in op sanitizer errors for `deno test`.
|
||||
*
|
||||
* **NOTE:** enabling tracing has a significant negative performance impact.
|
||||
* To get high level metrics on async ops with no added performance cost,
|
||||
* use `Deno.core.metrics()`.
|
||||
*/
|
||||
function enableOpCallTracing(): void;
|
||||
|
||||
export interface OpCallTrace {
|
||||
opName: string;
|
||||
stack: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* A map containing traces for all ongoing async ops. The key is the op id.
|
||||
* Tracing only occurs when `Deno.core.enableOpCallTracing()` was previously
|
||||
* enabled.
|
||||
*/
|
||||
const opCallTraces: Map<number, OpCallTrace>;
|
||||
|
||||
/**
|
||||
* Adds a callback for the given Promise event. If this function is called
|
||||
* multiple times, the callbacks are called in the order they were added.
|
||||
* - `init_hook` is called when a new promise is created. When a new promise
|
||||
* is created as part of the chain in the case of `Promise.then` or in the
|
||||
* intermediate promises created by `Promise.{race, all}`/`AsyncFunctionAwait`,
|
||||
* we pass the parent promise otherwise we pass undefined.
|
||||
* - `before_hook` is called at the beginning of the promise reaction.
|
||||
* - `after_hook` is called at the end of the promise reaction.
|
||||
* - `resolve_hook` is called at the beginning of resolve or reject function.
|
||||
*/
|
||||
function setPromiseHooks(
|
||||
init_hook?: (
|
||||
promise: Promise<unknown>,
|
||||
parentPromise?: Promise<unknown>,
|
||||
) => void,
|
||||
before_hook?: (promise: Promise<unknown>) => void,
|
||||
after_hook?: (promise: Promise<unknown>) => void,
|
||||
resolve_hook?: (promise: Promise<unknown>) => void,
|
||||
): void;
|
||||
|
||||
const build: {
|
||||
target: string;
|
||||
arch: string;
|
||||
os: string;
|
||||
vendor: string;
|
||||
env: string | undefined;
|
||||
};
|
||||
}
|
||||
}
|
204
core/lib.rs
204
core/lib.rs
|
@ -1,204 +0,0 @@
|
|||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
||||
mod async_cancel;
|
||||
mod async_cell;
|
||||
pub mod error;
|
||||
mod error_codes;
|
||||
mod extensions;
|
||||
mod fast_string;
|
||||
mod flags;
|
||||
mod gotham_state;
|
||||
mod inspector;
|
||||
mod io;
|
||||
mod joinset;
|
||||
mod module_specifier;
|
||||
mod modules;
|
||||
mod normalize_path;
|
||||
mod ops;
|
||||
mod ops_builtin;
|
||||
mod ops_builtin_v8;
|
||||
mod ops_metrics;
|
||||
mod path;
|
||||
mod resources;
|
||||
mod runtime;
|
||||
mod source_map;
|
||||
pub mod task;
|
||||
mod task_queue;
|
||||
|
||||
// Re-exports
|
||||
pub use anyhow;
|
||||
pub use futures;
|
||||
pub use parking_lot;
|
||||
pub use serde;
|
||||
pub use serde_json;
|
||||
pub use serde_v8;
|
||||
pub use serde_v8::ByteString;
|
||||
pub use serde_v8::DetachedBuffer;
|
||||
pub use serde_v8::JsBuffer;
|
||||
pub use serde_v8::StringOrBuffer;
|
||||
pub use serde_v8::ToJsBuffer;
|
||||
pub use serde_v8::U16String;
|
||||
pub use sourcemap;
|
||||
pub use url;
|
||||
pub use v8;
|
||||
|
||||
pub use deno_ops::op;
|
||||
pub use deno_ops::op2;
|
||||
|
||||
pub use crate::async_cancel::CancelFuture;
|
||||
pub use crate::async_cancel::CancelHandle;
|
||||
pub use crate::async_cancel::CancelTryFuture;
|
||||
pub use crate::async_cancel::Cancelable;
|
||||
pub use crate::async_cancel::Canceled;
|
||||
pub use crate::async_cancel::TryCancelable;
|
||||
pub use crate::async_cell::AsyncMut;
|
||||
pub use crate::async_cell::AsyncMutFuture;
|
||||
pub use crate::async_cell::AsyncRef;
|
||||
pub use crate::async_cell::AsyncRefCell;
|
||||
pub use crate::async_cell::AsyncRefFuture;
|
||||
pub use crate::async_cell::RcLike;
|
||||
pub use crate::async_cell::RcRef;
|
||||
pub use crate::error::GetErrorClassFn;
|
||||
pub use crate::error::JsErrorCreateFn;
|
||||
pub use crate::extensions::Extension;
|
||||
pub use crate::extensions::ExtensionBuilder;
|
||||
pub use crate::extensions::ExtensionFileSource;
|
||||
pub use crate::extensions::ExtensionFileSourceCode;
|
||||
pub use crate::extensions::OpDecl;
|
||||
pub use crate::extensions::OpMiddlewareFn;
|
||||
pub use crate::fast_string::FastString;
|
||||
pub use crate::flags::v8_set_flags;
|
||||
pub use crate::inspector::InspectorMsg;
|
||||
pub use crate::inspector::InspectorMsgKind;
|
||||
pub use crate::inspector::InspectorSessionProxy;
|
||||
pub use crate::inspector::JsRuntimeInspector;
|
||||
pub use crate::inspector::LocalInspectorSession;
|
||||
pub use crate::io::BufMutView;
|
||||
pub use crate::io::BufView;
|
||||
pub use crate::io::WriteOutcome;
|
||||
pub use crate::module_specifier::resolve_import;
|
||||
pub use crate::module_specifier::resolve_path;
|
||||
pub use crate::module_specifier::resolve_url;
|
||||
pub use crate::module_specifier::resolve_url_or_path;
|
||||
pub use crate::module_specifier::ModuleResolutionError;
|
||||
pub use crate::module_specifier::ModuleSpecifier;
|
||||
pub use crate::modules::ExtModuleLoaderCb;
|
||||
pub use crate::modules::FsModuleLoader;
|
||||
pub use crate::modules::ModuleCode;
|
||||
pub use crate::modules::ModuleId;
|
||||
pub use crate::modules::ModuleLoader;
|
||||
pub use crate::modules::ModuleSource;
|
||||
pub use crate::modules::ModuleSourceFuture;
|
||||
pub use crate::modules::ModuleType;
|
||||
pub use crate::modules::NoopModuleLoader;
|
||||
pub use crate::modules::ResolutionKind;
|
||||
pub use crate::normalize_path::normalize_path;
|
||||
pub use crate::ops::OpCall;
|
||||
pub use crate::ops::OpError;
|
||||
pub use crate::ops::OpId;
|
||||
pub use crate::ops::OpResult;
|
||||
pub use crate::ops::OpState;
|
||||
pub use crate::ops::PromiseId;
|
||||
pub use crate::ops_builtin::op_close;
|
||||
pub use crate::ops_builtin::op_print;
|
||||
pub use crate::ops_builtin::op_resources;
|
||||
pub use crate::ops_builtin::op_void_async;
|
||||
pub use crate::ops_builtin::op_void_sync;
|
||||
pub use crate::ops_metrics::OpsTracker;
|
||||
pub use crate::path::strip_unc_prefix;
|
||||
pub use crate::resources::AsyncResult;
|
||||
pub use crate::resources::Resource;
|
||||
pub use crate::resources::ResourceId;
|
||||
pub use crate::resources::ResourceTable;
|
||||
pub use crate::runtime::CompiledWasmModuleStore;
|
||||
pub use crate::runtime::CrossIsolateStore;
|
||||
pub use crate::runtime::JsRealm;
|
||||
pub use crate::runtime::JsRuntime;
|
||||
pub use crate::runtime::JsRuntimeForSnapshot;
|
||||
pub use crate::runtime::RuntimeOptions;
|
||||
pub use crate::runtime::SharedArrayBufferStore;
|
||||
pub use crate::runtime::Snapshot;
|
||||
pub use crate::runtime::V8_WRAPPER_OBJECT_INDEX;
|
||||
pub use crate::runtime::V8_WRAPPER_TYPE_INDEX;
|
||||
pub use crate::source_map::SourceMapGetter;
|
||||
pub use crate::task_queue::TaskQueue;
|
||||
pub use crate::task_queue::TaskQueuePermit;
|
||||
|
||||
pub fn v8_version() -> &'static str {
|
||||
v8::V8::get_version()
|
||||
}
|
||||
|
||||
/// An internal module re-exporting functions used by the #[op] (`deno_ops`) macro
|
||||
#[doc(hidden)]
|
||||
pub mod _ops {
|
||||
pub use super::error::throw_type_error;
|
||||
pub use super::error_codes::get_error_code;
|
||||
pub use super::extensions::Op;
|
||||
pub use super::extensions::OpDecl;
|
||||
pub use super::ops::to_op_result;
|
||||
pub use super::ops::OpCtx;
|
||||
pub use super::ops::OpResult;
|
||||
pub use super::runtime::ops::map_async_op1;
|
||||
pub use super::runtime::ops::map_async_op2;
|
||||
pub use super::runtime::ops::map_async_op3;
|
||||
pub use super::runtime::ops::map_async_op4;
|
||||
pub use super::runtime::ops::queue_async_op;
|
||||
pub use super::runtime::ops::queue_fast_async_op;
|
||||
pub use super::runtime::ops::to_i32;
|
||||
pub use super::runtime::ops::to_str;
|
||||
pub use super::runtime::ops::to_str_ptr;
|
||||
pub use super::runtime::ops::to_string_ptr;
|
||||
pub use super::runtime::ops::to_u32;
|
||||
pub use super::runtime::V8_WRAPPER_OBJECT_INDEX;
|
||||
pub use super::runtime::V8_WRAPPER_TYPE_INDEX;
|
||||
}
|
||||
|
||||
// TODO(mmastrac): Temporary while we move code around
|
||||
pub mod snapshot_util {
|
||||
pub use crate::runtime::create_snapshot;
|
||||
pub use crate::runtime::get_js_files;
|
||||
pub use crate::runtime::CreateSnapshotOptions;
|
||||
pub use crate::runtime::CreateSnapshotOutput;
|
||||
pub use crate::runtime::FilterFn;
|
||||
}
|
||||
|
||||
/// A helper macro that will return a call site in Rust code. Should be
|
||||
/// used when executing internal one-line scripts for JsRuntime lifecycle.
|
||||
///
|
||||
/// Returns a string in form of: "`[ext:<filename>:<line>:<column>]`"
|
||||
#[macro_export]
|
||||
macro_rules! located_script_name {
|
||||
() => {
|
||||
concat!(
|
||||
"[ext:",
|
||||
std::file!(),
|
||||
":",
|
||||
std::line!(),
|
||||
":",
|
||||
std::column!(),
|
||||
"]"
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn located_script_name() {
|
||||
// Note that this test will fail if this file is moved. We don't
|
||||
// test line locations because that's just too brittle.
|
||||
let name = located_script_name!();
|
||||
let expected = if cfg!(windows) {
|
||||
"[ext:core\\lib.rs:"
|
||||
} else {
|
||||
"[ext:core/lib.rs:"
|
||||
};
|
||||
assert_eq!(&name[..expected.len()], expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_v8_version() {
|
||||
assert!(v8_version().len() > 3);
|
||||
}
|
||||
}
|
|
@ -1,500 +0,0 @@
|
|||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
||||
|
||||
use crate::normalize_path;
|
||||
use std::error::Error;
|
||||
use std::fmt;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
use url::ParseError;
|
||||
use url::Url;
|
||||
|
||||
/// Error indicating the reason resolving a module specifier failed.
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub enum ModuleResolutionError {
|
||||
InvalidUrl(ParseError),
|
||||
InvalidBaseUrl(ParseError),
|
||||
InvalidPath(PathBuf),
|
||||
ImportPrefixMissing(String, Option<String>),
|
||||
}
|
||||
use ModuleResolutionError::*;
|
||||
|
||||
impl Error for ModuleResolutionError {
|
||||
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
||||
match self {
|
||||
InvalidUrl(ref err) | InvalidBaseUrl(ref err) => Some(err),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ModuleResolutionError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
InvalidUrl(ref err) => write!(f, "invalid URL: {err}"),
|
||||
InvalidBaseUrl(ref err) => {
|
||||
write!(f, "invalid base URL for relative import: {err}")
|
||||
}
|
||||
InvalidPath(ref path) => write!(f, "invalid module path: {path:?}"),
|
||||
ImportPrefixMissing(ref specifier, ref maybe_referrer) => write!(
|
||||
f,
|
||||
"Relative import path \"{}\" not prefixed with / or ./ or ../{}",
|
||||
specifier,
|
||||
match maybe_referrer {
|
||||
Some(referrer) => format!(" from \"{referrer}\""),
|
||||
None => String::new(),
|
||||
}
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Resolved module specifier
|
||||
pub type ModuleSpecifier = Url;
|
||||
|
||||
/// Resolves module using this algorithm:
|
||||
/// <https://html.spec.whatwg.org/multipage/webappapis.html#resolve-a-module-specifier>
|
||||
pub fn resolve_import(
|
||||
specifier: &str,
|
||||
base: &str,
|
||||
) -> Result<ModuleSpecifier, ModuleResolutionError> {
|
||||
let url = match Url::parse(specifier) {
|
||||
// 1. Apply the URL parser to specifier.
|
||||
// If the result is not failure, return he result.
|
||||
Ok(url) => url,
|
||||
|
||||
// 2. If specifier does not start with the character U+002F SOLIDUS (/),
|
||||
// the two-character sequence U+002E FULL STOP, U+002F SOLIDUS (./),
|
||||
// or the three-character sequence U+002E FULL STOP, U+002E FULL STOP,
|
||||
// U+002F SOLIDUS (../), return failure.
|
||||
Err(ParseError::RelativeUrlWithoutBase)
|
||||
if !(specifier.starts_with('/')
|
||||
|| specifier.starts_with("./")
|
||||
|| specifier.starts_with("../")) =>
|
||||
{
|
||||
let maybe_referrer = if base.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(base.to_string())
|
||||
};
|
||||
return Err(ImportPrefixMissing(specifier.to_string(), maybe_referrer));
|
||||
}
|
||||
|
||||
// 3. Return the result of applying the URL parser to specifier with base
|
||||
// URL as the base URL.
|
||||
Err(ParseError::RelativeUrlWithoutBase) => {
|
||||
let base = Url::parse(base).map_err(InvalidBaseUrl)?;
|
||||
base.join(specifier).map_err(InvalidUrl)?
|
||||
}
|
||||
|
||||
// If parsing the specifier as a URL failed for a different reason than
|
||||
// it being relative, always return the original error. We don't want to
|
||||
// return `ImportPrefixMissing` or `InvalidBaseUrl` if the real
|
||||
// problem lies somewhere else.
|
||||
Err(err) => return Err(InvalidUrl(err)),
|
||||
};
|
||||
|
||||
Ok(url)
|
||||
}
|
||||
|
||||
/// Converts a string representing an absolute URL into a ModuleSpecifier.
|
||||
pub fn resolve_url(
|
||||
url_str: &str,
|
||||
) -> Result<ModuleSpecifier, ModuleResolutionError> {
|
||||
Url::parse(url_str).map_err(ModuleResolutionError::InvalidUrl)
|
||||
}
|
||||
|
||||
/// Takes a string representing either an absolute URL or a file path,
|
||||
/// as it may be passed to deno as a command line argument.
|
||||
/// The string is interpreted as a URL if it starts with a valid URI scheme,
|
||||
/// e.g. 'http:' or 'file:' or 'git+ssh:'. If not, it's interpreted as a
|
||||
/// file path; if it is a relative path it's resolved relative to passed
|
||||
/// `current_dir`.
|
||||
pub fn resolve_url_or_path(
|
||||
specifier: &str,
|
||||
current_dir: &Path,
|
||||
) -> Result<ModuleSpecifier, ModuleResolutionError> {
|
||||
if specifier_has_uri_scheme(specifier) {
|
||||
resolve_url(specifier)
|
||||
} else {
|
||||
resolve_path(specifier, current_dir)
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts a string representing a relative or absolute path into a
|
||||
/// ModuleSpecifier. A relative path is considered relative to the passed
|
||||
/// `current_dir`.
|
||||
pub fn resolve_path(
|
||||
path_str: &str,
|
||||
current_dir: &Path,
|
||||
) -> Result<ModuleSpecifier, ModuleResolutionError> {
|
||||
let path = current_dir.join(path_str);
|
||||
let path = normalize_path(path);
|
||||
Url::from_file_path(&path)
|
||||
.map_err(|()| ModuleResolutionError::InvalidPath(path))
|
||||
}
|
||||
|
||||
/// Returns true if the input string starts with a sequence of characters
|
||||
/// that could be a valid URI scheme, like 'https:', 'git+ssh:' or 'data:'.
|
||||
///
|
||||
/// According to RFC 3986 (https://tools.ietf.org/html/rfc3986#section-3.1),
|
||||
/// a valid scheme has the following format:
|
||||
/// scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
|
||||
///
|
||||
/// We additionally require the scheme to be at least 2 characters long,
|
||||
/// because otherwise a windows path like c:/foo would be treated as a URL,
|
||||
/// while no schemes with a one-letter name actually exist.
|
||||
fn specifier_has_uri_scheme(specifier: &str) -> bool {
|
||||
let mut chars = specifier.chars();
|
||||
let mut len = 0usize;
|
||||
// THe first character must be a letter.
|
||||
match chars.next() {
|
||||
Some(c) if c.is_ascii_alphabetic() => len += 1,
|
||||
_ => return false,
|
||||
}
|
||||
// Second and following characters must be either a letter, number,
|
||||
// plus sign, minus sign, or dot.
|
||||
loop {
|
||||
match chars.next() {
|
||||
Some(c) if c.is_ascii_alphanumeric() || "+-.".contains(c) => len += 1,
|
||||
Some(':') if len >= 2 => return true,
|
||||
_ => return false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::serde_json::from_value;
|
||||
use crate::serde_json::json;
|
||||
use std::env::current_dir;
|
||||
use std::path::Path;
|
||||
|
||||
#[test]
|
||||
fn test_resolve_import() {
|
||||
let tests = vec![
|
||||
(
|
||||
"./005_more_imports.ts",
|
||||
"http://deno.land/core/tests/006_url_imports.ts",
|
||||
"http://deno.land/core/tests/005_more_imports.ts",
|
||||
),
|
||||
(
|
||||
"../005_more_imports.ts",
|
||||
"http://deno.land/core/tests/006_url_imports.ts",
|
||||
"http://deno.land/core/005_more_imports.ts",
|
||||
),
|
||||
(
|
||||
"http://deno.land/core/tests/005_more_imports.ts",
|
||||
"http://deno.land/core/tests/006_url_imports.ts",
|
||||
"http://deno.land/core/tests/005_more_imports.ts",
|
||||
),
|
||||
(
|
||||
"data:text/javascript,export default 'grapes';",
|
||||
"http://deno.land/core/tests/006_url_imports.ts",
|
||||
"data:text/javascript,export default 'grapes';",
|
||||
),
|
||||
(
|
||||
"blob:https://whatwg.org/d0360e2f-caee-469f-9a2f-87d5b0456f6f",
|
||||
"http://deno.land/core/tests/006_url_imports.ts",
|
||||
"blob:https://whatwg.org/d0360e2f-caee-469f-9a2f-87d5b0456f6f",
|
||||
),
|
||||
(
|
||||
"javascript:export default 'artichokes';",
|
||||
"http://deno.land/core/tests/006_url_imports.ts",
|
||||
"javascript:export default 'artichokes';",
|
||||
),
|
||||
(
|
||||
"data:text/plain,export default 'kale';",
|
||||
"http://deno.land/core/tests/006_url_imports.ts",
|
||||
"data:text/plain,export default 'kale';",
|
||||
),
|
||||
(
|
||||
"/dev/core/tests/005_more_imports.ts",
|
||||
"file:///home/yeti",
|
||||
"file:///dev/core/tests/005_more_imports.ts",
|
||||
),
|
||||
(
|
||||
"//zombo.com/1999.ts",
|
||||
"https://cherry.dev/its/a/thing",
|
||||
"https://zombo.com/1999.ts",
|
||||
),
|
||||
(
|
||||
"http://deno.land/this/url/is/valid",
|
||||
"base is clearly not a valid url",
|
||||
"http://deno.land/this/url/is/valid",
|
||||
),
|
||||
(
|
||||
"//server/some/dir/file",
|
||||
"file:///home/yeti/deno",
|
||||
"file://server/some/dir/file",
|
||||
),
|
||||
// This test is disabled because the url crate does not follow the spec,
|
||||
// dropping the server part from the final result.
|
||||
// (
|
||||
// "/another/path/at/the/same/server",
|
||||
// "file://server/some/dir/file",
|
||||
// "file://server/another/path/at/the/same/server",
|
||||
// ),
|
||||
];
|
||||
|
||||
for (specifier, base, expected_url) in tests {
|
||||
let url = resolve_import(specifier, base).unwrap().to_string();
|
||||
assert_eq!(url, expected_url);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_resolve_import_error() {
|
||||
use url::ParseError::*;
|
||||
use ModuleResolutionError::*;
|
||||
|
||||
let tests = vec![
|
||||
(
|
||||
"awesome.ts",
|
||||
"<unknown>",
|
||||
ImportPrefixMissing(
|
||||
"awesome.ts".to_string(),
|
||||
Some("<unknown>".to_string()),
|
||||
),
|
||||
),
|
||||
(
|
||||
"005_more_imports.ts",
|
||||
"http://deno.land/core/tests/006_url_imports.ts",
|
||||
ImportPrefixMissing(
|
||||
"005_more_imports.ts".to_string(),
|
||||
Some("http://deno.land/core/tests/006_url_imports.ts".to_string()),
|
||||
),
|
||||
),
|
||||
(
|
||||
".tomato",
|
||||
"http://deno.land/core/tests/006_url_imports.ts",
|
||||
ImportPrefixMissing(
|
||||
".tomato".to_string(),
|
||||
Some("http://deno.land/core/tests/006_url_imports.ts".to_string()),
|
||||
),
|
||||
),
|
||||
(
|
||||
"..zucchini.mjs",
|
||||
"http://deno.land/core/tests/006_url_imports.ts",
|
||||
ImportPrefixMissing(
|
||||
"..zucchini.mjs".to_string(),
|
||||
Some("http://deno.land/core/tests/006_url_imports.ts".to_string()),
|
||||
),
|
||||
),
|
||||
(
|
||||
r".\yam.es",
|
||||
"http://deno.land/core/tests/006_url_imports.ts",
|
||||
ImportPrefixMissing(
|
||||
r".\yam.es".to_string(),
|
||||
Some("http://deno.land/core/tests/006_url_imports.ts".to_string()),
|
||||
),
|
||||
),
|
||||
(
|
||||
r"..\yam.es",
|
||||
"http://deno.land/core/tests/006_url_imports.ts",
|
||||
ImportPrefixMissing(
|
||||
r"..\yam.es".to_string(),
|
||||
Some("http://deno.land/core/tests/006_url_imports.ts".to_string()),
|
||||
),
|
||||
),
|
||||
(
|
||||
"https://eggplant:b/c",
|
||||
"http://deno.land/core/tests/006_url_imports.ts",
|
||||
InvalidUrl(InvalidPort),
|
||||
),
|
||||
(
|
||||
"https://eggplant@/c",
|
||||
"http://deno.land/core/tests/006_url_imports.ts",
|
||||
InvalidUrl(EmptyHost),
|
||||
),
|
||||
(
|
||||
"./foo.ts",
|
||||
"/relative/base/url",
|
||||
InvalidBaseUrl(RelativeUrlWithoutBase),
|
||||
),
|
||||
];
|
||||
|
||||
for (specifier, base, expected_err) in tests {
|
||||
let err = resolve_import(specifier, base).unwrap_err();
|
||||
assert_eq!(err, expected_err);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_resolve_url_or_path() {
|
||||
// Absolute URL.
|
||||
let mut tests: Vec<(&str, String)> = vec![
|
||||
(
|
||||
"http://deno.land/core/tests/006_url_imports.ts",
|
||||
"http://deno.land/core/tests/006_url_imports.ts".to_string(),
|
||||
),
|
||||
(
|
||||
"https://deno.land/core/tests/006_url_imports.ts",
|
||||
"https://deno.land/core/tests/006_url_imports.ts".to_string(),
|
||||
),
|
||||
];
|
||||
|
||||
// The local path tests assume that the cwd is the deno repo root.
|
||||
let cwd = current_dir().unwrap();
|
||||
let cwd_str = cwd.to_str().unwrap();
|
||||
|
||||
if cfg!(target_os = "windows") {
|
||||
// Absolute local path.
|
||||
let expected_url = "file:///C:/deno/tests/006_url_imports.ts";
|
||||
tests.extend(vec![
|
||||
(
|
||||
r"C:/deno/tests/006_url_imports.ts",
|
||||
expected_url.to_string(),
|
||||
),
|
||||
(
|
||||
r"C:\deno\tests\006_url_imports.ts",
|
||||
expected_url.to_string(),
|
||||
),
|
||||
(
|
||||
r"\\?\C:\deno\tests\006_url_imports.ts",
|
||||
expected_url.to_string(),
|
||||
),
|
||||
// Not supported: `Url::from_file_path()` fails.
|
||||
// (r"\\.\C:\deno\tests\006_url_imports.ts", expected_url.to_string()),
|
||||
// Not supported: `Url::from_file_path()` performs the wrong conversion.
|
||||
// (r"//./C:/deno/tests/006_url_imports.ts", expected_url.to_string()),
|
||||
]);
|
||||
|
||||
// Rooted local path without drive letter.
|
||||
let expected_url = format!(
|
||||
"file:///{}:/deno/tests/006_url_imports.ts",
|
||||
cwd_str.get(..1).unwrap(),
|
||||
);
|
||||
tests.extend(vec![
|
||||
(r"/deno/tests/006_url_imports.ts", expected_url.to_string()),
|
||||
(r"\deno\tests\006_url_imports.ts", expected_url.to_string()),
|
||||
(
|
||||
r"\deno\..\deno\tests\006_url_imports.ts",
|
||||
expected_url.to_string(),
|
||||
),
|
||||
(r"\deno\.\tests\006_url_imports.ts", expected_url),
|
||||
]);
|
||||
|
||||
// Relative local path.
|
||||
let expected_url = format!(
|
||||
"file:///{}/tests/006_url_imports.ts",
|
||||
cwd_str.replace('\\', "/")
|
||||
);
|
||||
tests.extend(vec![
|
||||
(r"tests/006_url_imports.ts", expected_url.to_string()),
|
||||
(r"tests\006_url_imports.ts", expected_url.to_string()),
|
||||
(r"./tests/006_url_imports.ts", (*expected_url).to_string()),
|
||||
(r".\tests\006_url_imports.ts", (*expected_url).to_string()),
|
||||
]);
|
||||
|
||||
// UNC network path.
|
||||
let expected_url = "file://server/share/deno/cool";
|
||||
tests.extend(vec![
|
||||
(r"\\server\share\deno\cool", expected_url.to_string()),
|
||||
(r"\\server/share/deno/cool", expected_url.to_string()),
|
||||
// Not supported: `Url::from_file_path()` performs the wrong conversion.
|
||||
// (r"//server/share/deno/cool", expected_url.to_string()),
|
||||
]);
|
||||
} else {
|
||||
// Absolute local path.
|
||||
let expected_url = "file:///deno/tests/006_url_imports.ts";
|
||||
tests.extend(vec![
|
||||
("/deno/tests/006_url_imports.ts", expected_url.to_string()),
|
||||
("//deno/tests/006_url_imports.ts", expected_url.to_string()),
|
||||
]);
|
||||
|
||||
// Relative local path.
|
||||
let expected_url = format!("file://{cwd_str}/tests/006_url_imports.ts");
|
||||
tests.extend(vec![
|
||||
("tests/006_url_imports.ts", expected_url.to_string()),
|
||||
("./tests/006_url_imports.ts", expected_url.to_string()),
|
||||
(
|
||||
"tests/../tests/006_url_imports.ts",
|
||||
expected_url.to_string(),
|
||||
),
|
||||
("tests/./006_url_imports.ts", expected_url),
|
||||
]);
|
||||
}
|
||||
|
||||
for (specifier, expected_url) in tests {
|
||||
let url = resolve_url_or_path(specifier, &cwd).unwrap().to_string();
|
||||
assert_eq!(url, expected_url);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_resolve_url_or_path_deprecated_error() {
|
||||
use url::ParseError::*;
|
||||
use ModuleResolutionError::*;
|
||||
|
||||
let mut tests = vec![
|
||||
("https://eggplant:b/c", InvalidUrl(InvalidPort)),
|
||||
("https://:8080/a/b/c", InvalidUrl(EmptyHost)),
|
||||
];
|
||||
if cfg!(target_os = "windows") {
|
||||
let p = r"\\.\c:/stuff/deno/script.ts";
|
||||
tests.push((p, InvalidPath(PathBuf::from(p))));
|
||||
}
|
||||
|
||||
for (specifier, expected_err) in tests {
|
||||
let err =
|
||||
resolve_url_or_path(specifier, &PathBuf::from("/")).unwrap_err();
|
||||
assert_eq!(err, expected_err);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_specifier_has_uri_scheme() {
|
||||
let tests = vec![
|
||||
("http://foo.bar/etc", true),
|
||||
("HTTP://foo.bar/etc", true),
|
||||
("http:ftp:", true),
|
||||
("http:", true),
|
||||
("hTtP:", true),
|
||||
("ftp:", true),
|
||||
("mailto:spam@please.me", true),
|
||||
("git+ssh://git@github.com/denoland/deno", true),
|
||||
("blob:https://whatwg.org/mumbojumbo", true),
|
||||
("abc.123+DEF-ghi:", true),
|
||||
("abc.123+def-ghi:@", true),
|
||||
("", false),
|
||||
(":not", false),
|
||||
("http", false),
|
||||
("c:dir", false),
|
||||
("X:", false),
|
||||
("./http://not", false),
|
||||
("1abc://kinda/but/no", false),
|
||||
("schluẞ://no/more", false),
|
||||
];
|
||||
|
||||
for (specifier, expected) in tests {
|
||||
let result = specifier_has_uri_scheme(specifier);
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_normalize_path() {
|
||||
assert_eq!(normalize_path(Path::new("a/../b")), PathBuf::from("b"));
|
||||
assert_eq!(normalize_path(Path::new("a/./b/")), PathBuf::from("a/b/"));
|
||||
assert_eq!(
|
||||
normalize_path(Path::new("a/./b/../c")),
|
||||
PathBuf::from("a/c")
|
||||
);
|
||||
|
||||
if cfg!(windows) {
|
||||
assert_eq!(
|
||||
normalize_path(Path::new("C:\\a\\.\\b\\..\\c")),
|
||||
PathBuf::from("C:\\a\\c")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_deserialize_module_specifier() {
|
||||
let actual: ModuleSpecifier =
|
||||
from_value(json!("http://deno.land/x/mod.ts")).unwrap();
|
||||
let expected = resolve_url("http://deno.land/x/mod.ts").unwrap();
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
}
|
|
@ -1,253 +0,0 @@
|
|||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
||||
use crate::error::generic_error;
|
||||
use crate::error::AnyError;
|
||||
use crate::extensions::ExtensionFileSource;
|
||||
use crate::module_specifier::ModuleSpecifier;
|
||||
use crate::modules::ModuleCode;
|
||||
use crate::modules::ModuleSource;
|
||||
use crate::modules::ModuleSourceFuture;
|
||||
use crate::modules::ModuleType;
|
||||
use crate::modules::ResolutionKind;
|
||||
use crate::resolve_import;
|
||||
use crate::Extension;
|
||||
use anyhow::anyhow;
|
||||
use anyhow::Error;
|
||||
use futures::future::FutureExt;
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
use std::collections::HashSet;
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::rc::Rc;
|
||||
|
||||
pub trait ModuleLoader {
|
||||
/// Returns an absolute URL.
|
||||
/// When implementing an spec-complaint VM, this should be exactly the
|
||||
/// algorithm described here:
|
||||
/// <https://html.spec.whatwg.org/multipage/webappapis.html#resolve-a-module-specifier>
|
||||
///
|
||||
/// `is_main` can be used to resolve from current working directory or
|
||||
/// apply import map for child imports.
|
||||
///
|
||||
/// `is_dyn_import` can be used to check permissions or deny
|
||||
/// dynamic imports altogether.
|
||||
fn resolve(
|
||||
&self,
|
||||
specifier: &str,
|
||||
referrer: &str,
|
||||
kind: ResolutionKind,
|
||||
) -> Result<ModuleSpecifier, Error>;
|
||||
|
||||
/// Given ModuleSpecifier, load its source code.
|
||||
///
|
||||
/// `is_dyn_import` can be used to check permissions or deny
|
||||
/// dynamic imports altogether.
|
||||
fn load(
|
||||
&self,
|
||||
module_specifier: &ModuleSpecifier,
|
||||
maybe_referrer: Option<&ModuleSpecifier>,
|
||||
is_dyn_import: bool,
|
||||
) -> Pin<Box<ModuleSourceFuture>>;
|
||||
|
||||
/// This hook can be used by implementors to do some preparation
|
||||
/// work before starting loading of modules.
|
||||
///
|
||||
/// For example implementor might download multiple modules in
|
||||
/// parallel and transpile them to final JS sources before
|
||||
/// yielding control back to the runtime.
|
||||
///
|
||||
/// It's not required to implement this method.
|
||||
fn prepare_load(
|
||||
&self,
|
||||
_module_specifier: &ModuleSpecifier,
|
||||
_maybe_referrer: Option<String>,
|
||||
_is_dyn_import: bool,
|
||||
) -> Pin<Box<dyn Future<Output = Result<(), Error>>>> {
|
||||
async { Ok(()) }.boxed_local()
|
||||
}
|
||||
}
|
||||
|
||||
/// Placeholder structure used when creating
|
||||
/// a runtime that doesn't support module loading.
|
||||
pub struct NoopModuleLoader;
|
||||
|
||||
impl ModuleLoader for NoopModuleLoader {
|
||||
fn resolve(
|
||||
&self,
|
||||
specifier: &str,
|
||||
referrer: &str,
|
||||
_kind: ResolutionKind,
|
||||
) -> Result<ModuleSpecifier, Error> {
|
||||
Err(generic_error(
|
||||
format!("Module loading is not supported; attempted to resolve: \"{specifier}\" from \"{referrer}\"")
|
||||
))
|
||||
}
|
||||
|
||||
fn load(
|
||||
&self,
|
||||
module_specifier: &ModuleSpecifier,
|
||||
maybe_referrer: Option<&ModuleSpecifier>,
|
||||
_is_dyn_import: bool,
|
||||
) -> Pin<Box<ModuleSourceFuture>> {
|
||||
let err = generic_error(
|
||||
format!(
|
||||
"Module loading is not supported; attempted to load: \"{module_specifier}\" from \"{maybe_referrer:?}\"",
|
||||
)
|
||||
);
|
||||
async move { Err(err) }.boxed_local()
|
||||
}
|
||||
}
|
||||
|
||||
/// Function that can be passed to the `ExtModuleLoader` that allows to
|
||||
/// transpile sources before passing to V8.
|
||||
pub type ExtModuleLoaderCb =
|
||||
Box<dyn Fn(&ExtensionFileSource) -> Result<ModuleCode, Error>>;
|
||||
|
||||
pub(crate) struct ExtModuleLoader {
|
||||
maybe_load_callback: Option<Rc<ExtModuleLoaderCb>>,
|
||||
sources: RefCell<HashMap<String, ExtensionFileSource>>,
|
||||
used_specifiers: RefCell<HashSet<String>>,
|
||||
}
|
||||
|
||||
impl ExtModuleLoader {
|
||||
pub fn new(
|
||||
extensions: &[Extension],
|
||||
maybe_load_callback: Option<Rc<ExtModuleLoaderCb>>,
|
||||
) -> Self {
|
||||
let mut sources = HashMap::new();
|
||||
sources.extend(
|
||||
extensions
|
||||
.iter()
|
||||
.flat_map(|e| e.get_esm_sources())
|
||||
.map(|s| (s.specifier.to_string(), s.clone())),
|
||||
);
|
||||
ExtModuleLoader {
|
||||
maybe_load_callback,
|
||||
sources: RefCell::new(sources),
|
||||
used_specifiers: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ModuleLoader for ExtModuleLoader {
|
||||
fn resolve(
|
||||
&self,
|
||||
specifier: &str,
|
||||
referrer: &str,
|
||||
_kind: ResolutionKind,
|
||||
) -> Result<ModuleSpecifier, Error> {
|
||||
Ok(resolve_import(specifier, referrer)?)
|
||||
}
|
||||
|
||||
fn load(
|
||||
&self,
|
||||
specifier: &ModuleSpecifier,
|
||||
_maybe_referrer: Option<&ModuleSpecifier>,
|
||||
_is_dyn_import: bool,
|
||||
) -> Pin<Box<ModuleSourceFuture>> {
|
||||
let sources = self.sources.borrow();
|
||||
let source = match sources.get(specifier.as_str()) {
|
||||
Some(source) => source,
|
||||
None => return futures::future::err(anyhow!("Specifier \"{}\" was not passed as an extension module and was not included in the snapshot.", specifier)).boxed_local(),
|
||||
};
|
||||
self
|
||||
.used_specifiers
|
||||
.borrow_mut()
|
||||
.insert(specifier.to_string());
|
||||
let result = if let Some(load_callback) = &self.maybe_load_callback {
|
||||
load_callback(source)
|
||||
} else {
|
||||
source.load()
|
||||
};
|
||||
match result {
|
||||
Ok(code) => {
|
||||
let res = ModuleSource::new(ModuleType::JavaScript, code, specifier);
|
||||
return futures::future::ok(res).boxed_local();
|
||||
}
|
||||
Err(err) => return futures::future::err(err).boxed_local(),
|
||||
}
|
||||
}
|
||||
|
||||
fn prepare_load(
|
||||
&self,
|
||||
_specifier: &ModuleSpecifier,
|
||||
_maybe_referrer: Option<String>,
|
||||
_is_dyn_import: bool,
|
||||
) -> Pin<Box<dyn Future<Output = Result<(), Error>>>> {
|
||||
async { Ok(()) }.boxed_local()
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for ExtModuleLoader {
|
||||
fn drop(&mut self) {
|
||||
let sources = self.sources.get_mut();
|
||||
let used_specifiers = self.used_specifiers.get_mut();
|
||||
let unused_modules: Vec<_> = sources
|
||||
.iter()
|
||||
.filter(|(k, _)| !used_specifiers.contains(k.as_str()))
|
||||
.collect();
|
||||
|
||||
if !unused_modules.is_empty() {
|
||||
let mut msg =
|
||||
"Following modules were passed to ExtModuleLoader but never used:\n"
|
||||
.to_string();
|
||||
for m in unused_modules {
|
||||
msg.push_str(" - ");
|
||||
msg.push_str(m.0);
|
||||
msg.push('\n');
|
||||
}
|
||||
panic!("{}", msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Basic file system module loader.
|
||||
///
|
||||
/// Note that this loader will **block** event loop
|
||||
/// when loading file as it uses synchronous FS API
|
||||
/// from standard library.
|
||||
pub struct FsModuleLoader;
|
||||
|
||||
impl ModuleLoader for FsModuleLoader {
|
||||
fn resolve(
|
||||
&self,
|
||||
specifier: &str,
|
||||
referrer: &str,
|
||||
_kind: ResolutionKind,
|
||||
) -> Result<ModuleSpecifier, Error> {
|
||||
Ok(resolve_import(specifier, referrer)?)
|
||||
}
|
||||
|
||||
fn load(
|
||||
&self,
|
||||
module_specifier: &ModuleSpecifier,
|
||||
_maybe_referrer: Option<&ModuleSpecifier>,
|
||||
_is_dynamic: bool,
|
||||
) -> Pin<Box<ModuleSourceFuture>> {
|
||||
fn load(
|
||||
module_specifier: &ModuleSpecifier,
|
||||
) -> Result<ModuleSource, AnyError> {
|
||||
let path = module_specifier.to_file_path().map_err(|_| {
|
||||
generic_error(format!(
|
||||
"Provided module specifier \"{module_specifier}\" is not a file URL."
|
||||
))
|
||||
})?;
|
||||
let module_type = if let Some(extension) = path.extension() {
|
||||
let ext = extension.to_string_lossy().to_lowercase();
|
||||
if ext == "json" {
|
||||
ModuleType::Json
|
||||
} else {
|
||||
ModuleType::JavaScript
|
||||
}
|
||||
} else {
|
||||
ModuleType::JavaScript
|
||||
};
|
||||
|
||||
let code = std::fs::read_to_string(path)?.into();
|
||||
let module = ModuleSource::new(module_type, code, module_specifier);
|
||||
Ok(module)
|
||||
}
|
||||
|
||||
futures::future::ready(load(module_specifier)).boxed_local()
|
||||
}
|
||||
}
|
1014
core/modules/map.rs
1014
core/modules/map.rs
File diff suppressed because it is too large
Load diff
|
@ -1,689 +0,0 @@
|
|||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
||||
use crate::error::generic_error;
|
||||
use crate::fast_string::FastString;
|
||||
use crate::module_specifier::ModuleSpecifier;
|
||||
use crate::resolve_url;
|
||||
use anyhow::Error;
|
||||
use futures::future::FutureExt;
|
||||
use futures::stream::FuturesUnordered;
|
||||
use futures::stream::Stream;
|
||||
use futures::stream::TryStreamExt;
|
||||
use log::debug;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
use std::collections::HashSet;
|
||||
use std::collections::VecDeque;
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::rc::Rc;
|
||||
use std::task::Context;
|
||||
use std::task::Poll;
|
||||
|
||||
mod loaders;
|
||||
mod map;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
pub(crate) use loaders::ExtModuleLoader;
|
||||
pub use loaders::ExtModuleLoaderCb;
|
||||
pub use loaders::FsModuleLoader;
|
||||
pub use loaders::ModuleLoader;
|
||||
pub use loaders::NoopModuleLoader;
|
||||
pub(crate) use map::ModuleMap;
|
||||
#[cfg(test)]
|
||||
pub(crate) use map::SymbolicModule;
|
||||
|
||||
pub type ModuleId = usize;
|
||||
pub(crate) type ModuleLoadId = i32;
|
||||
pub type ModuleCode = FastString;
|
||||
pub type ModuleName = FastString;
|
||||
|
||||
const SUPPORTED_TYPE_ASSERTIONS: &[&str] = &["json"];
|
||||
|
||||
/// Throws V8 exception if assertions are invalid
|
||||
pub(crate) fn validate_import_assertions(
|
||||
scope: &mut v8::HandleScope,
|
||||
assertions: &HashMap<String, String>,
|
||||
) {
|
||||
for (key, value) in assertions {
|
||||
if key == "type" && !SUPPORTED_TYPE_ASSERTIONS.contains(&value.as_str()) {
|
||||
let message = v8::String::new(
|
||||
scope,
|
||||
&format!("\"{value}\" is not a valid module type."),
|
||||
)
|
||||
.unwrap();
|
||||
let exception = v8::Exception::type_error(scope, message);
|
||||
scope.throw_exception(exception);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum ImportAssertionsKind {
|
||||
StaticImport,
|
||||
DynamicImport,
|
||||
}
|
||||
|
||||
pub(crate) fn parse_import_assertions(
|
||||
scope: &mut v8::HandleScope,
|
||||
import_assertions: v8::Local<v8::FixedArray>,
|
||||
kind: ImportAssertionsKind,
|
||||
) -> HashMap<String, String> {
|
||||
let mut assertions: HashMap<String, String> = HashMap::default();
|
||||
|
||||
let assertions_per_line = match kind {
|
||||
// For static imports, assertions are triples of (keyword, value and source offset)
|
||||
// Also used in `module_resolve_callback`.
|
||||
ImportAssertionsKind::StaticImport => 3,
|
||||
// For dynamic imports, assertions are tuples of (keyword, value)
|
||||
ImportAssertionsKind::DynamicImport => 2,
|
||||
};
|
||||
assert_eq!(import_assertions.length() % assertions_per_line, 0);
|
||||
let no_of_assertions = import_assertions.length() / assertions_per_line;
|
||||
|
||||
for i in 0..no_of_assertions {
|
||||
let assert_key = import_assertions
|
||||
.get(scope, assertions_per_line * i)
|
||||
.unwrap();
|
||||
let assert_key_val = v8::Local::<v8::Value>::try_from(assert_key).unwrap();
|
||||
let assert_value = import_assertions
|
||||
.get(scope, (assertions_per_line * i) + 1)
|
||||
.unwrap();
|
||||
let assert_value_val =
|
||||
v8::Local::<v8::Value>::try_from(assert_value).unwrap();
|
||||
assertions.insert(
|
||||
assert_key_val.to_rust_string_lossy(scope),
|
||||
assert_value_val.to_rust_string_lossy(scope),
|
||||
);
|
||||
}
|
||||
|
||||
assertions
|
||||
}
|
||||
|
||||
pub(crate) fn get_asserted_module_type_from_assertions(
|
||||
assertions: &HashMap<String, String>,
|
||||
) -> AssertedModuleType {
|
||||
assertions
|
||||
.get("type")
|
||||
.map(|ty| {
|
||||
if ty == "json" {
|
||||
AssertedModuleType::Json
|
||||
} else {
|
||||
AssertedModuleType::JavaScriptOrWasm
|
||||
}
|
||||
})
|
||||
.unwrap_or(AssertedModuleType::JavaScriptOrWasm)
|
||||
}
|
||||
|
||||
/// A type of module to be executed.
|
||||
///
|
||||
/// For non-`JavaScript` modules, this value doesn't tell
|
||||
/// how to interpret the module; it is only used to validate
|
||||
/// the module against an import assertion (if one is present
|
||||
/// in the import statement).
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
|
||||
#[repr(u32)]
|
||||
pub enum ModuleType {
|
||||
JavaScript,
|
||||
Json,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for ModuleType {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::JavaScript => write!(f, "JavaScript"),
|
||||
Self::Json => write!(f, "JSON"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// EsModule source code that will be loaded into V8.
|
||||
///
|
||||
/// Users can implement `Into<ModuleInfo>` for different file types that
|
||||
/// can be transpiled to valid EsModule.
|
||||
///
|
||||
/// Found module URL might be different from specified URL
|
||||
/// used for loading due to redirections (like HTTP 303).
|
||||
/// Eg. Both "`https://example.com/a.ts`" and
|
||||
/// "`https://example.com/b.ts`" may point to "`https://example.com/c.ts`"
|
||||
/// By keeping track of specified and found URL we can alias modules and avoid
|
||||
/// recompiling the same code 3 times.
|
||||
// TODO(bartlomieju): I have a strong opinion we should store all redirects
|
||||
// that happened; not only first and final target. It would simplify a lot
|
||||
// of things throughout the codebase otherwise we may end up requesting
|
||||
// intermediate redirects from file loader.
|
||||
// NOTE: This should _not_ be made #[derive(Clone)] unless we take some precautions to avoid excessive string copying.
|
||||
#[derive(Debug)]
|
||||
pub struct ModuleSource {
|
||||
pub code: ModuleCode,
|
||||
pub module_type: ModuleType,
|
||||
module_url_specified: ModuleName,
|
||||
/// If the module was found somewhere other than the specified address, this will be [`Some`].
|
||||
module_url_found: Option<ModuleName>,
|
||||
}
|
||||
|
||||
impl ModuleSource {
|
||||
/// Create a [`ModuleSource`] without a redirect.
|
||||
pub fn new(
|
||||
module_type: impl Into<ModuleType>,
|
||||
code: ModuleCode,
|
||||
specifier: &ModuleSpecifier,
|
||||
) -> Self {
|
||||
let module_url_specified = specifier.as_ref().to_owned().into();
|
||||
Self {
|
||||
code,
|
||||
module_type: module_type.into(),
|
||||
module_url_specified,
|
||||
module_url_found: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a [`ModuleSource`] with a potential redirect. If the `specifier_found` parameter is the same as the
|
||||
/// specifier, the code behaves the same was as `ModuleSource::new`.
|
||||
pub fn new_with_redirect(
|
||||
module_type: impl Into<ModuleType>,
|
||||
code: ModuleCode,
|
||||
specifier: &ModuleSpecifier,
|
||||
specifier_found: &ModuleSpecifier,
|
||||
) -> Self {
|
||||
let module_url_found = if specifier == specifier_found {
|
||||
None
|
||||
} else {
|
||||
Some(specifier_found.as_ref().to_owned().into())
|
||||
};
|
||||
let module_url_specified = specifier.as_ref().to_owned().into();
|
||||
Self {
|
||||
code,
|
||||
module_type: module_type.into(),
|
||||
module_url_specified,
|
||||
module_url_found,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn for_test(code: &'static str, file: impl AsRef<str>) -> Self {
|
||||
Self {
|
||||
code: ModuleCode::from_static(code),
|
||||
module_type: ModuleType::JavaScript,
|
||||
module_url_specified: file.as_ref().to_owned().into(),
|
||||
module_url_found: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// If the `found` parameter is the same as the `specified` parameter, the code behaves the same was as `ModuleSource::for_test`.
|
||||
#[cfg(test)]
|
||||
pub fn for_test_with_redirect(
|
||||
code: &'static str,
|
||||
specified: impl AsRef<str>,
|
||||
found: impl AsRef<str>,
|
||||
) -> Self {
|
||||
let specified = specified.as_ref().to_string();
|
||||
let found = found.as_ref().to_string();
|
||||
let found = if found == specified {
|
||||
None
|
||||
} else {
|
||||
Some(found.into())
|
||||
};
|
||||
Self {
|
||||
code: ModuleCode::from_static(code),
|
||||
module_type: ModuleType::JavaScript,
|
||||
module_url_specified: specified.into(),
|
||||
module_url_found: found,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) type PrepareLoadFuture =
|
||||
dyn Future<Output = (ModuleLoadId, Result<RecursiveModuleLoad, Error>)>;
|
||||
pub type ModuleSourceFuture = dyn Future<Output = Result<ModuleSource, Error>>;
|
||||
|
||||
type ModuleLoadFuture =
|
||||
dyn Future<Output = Result<(ModuleRequest, ModuleSource), Error>>;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum ResolutionKind {
|
||||
/// This kind is used in only one situation: when a module is loaded via
|
||||
/// `JsRuntime::load_main_module` and is the top-level module, ie. the one
|
||||
/// passed as an argument to `JsRuntime::load_main_module`.
|
||||
MainModule,
|
||||
/// This kind is returned for all other modules during module load, that are
|
||||
/// static imports.
|
||||
Import,
|
||||
/// This kind is returned for all modules that are loaded as a result of a
|
||||
/// call to `import()` API (ie. top-level module as well as all its
|
||||
/// dependencies, and any other `import()` calls from that load).
|
||||
DynamicImport,
|
||||
}
|
||||
|
||||
/// Describes the entrypoint of a recursive module load.
|
||||
#[derive(Debug)]
|
||||
enum LoadInit {
|
||||
/// Main module specifier.
|
||||
Main(String),
|
||||
/// Module specifier for side module.
|
||||
Side(String),
|
||||
/// Dynamic import specifier with referrer and expected
|
||||
/// module type (which is determined by import assertion).
|
||||
DynamicImport(String, String, AssertedModuleType),
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
pub enum LoadState {
|
||||
Init,
|
||||
LoadingRoot,
|
||||
LoadingImports,
|
||||
Done,
|
||||
}
|
||||
|
||||
/// This future is used to implement parallel async module loading.
|
||||
pub(crate) struct RecursiveModuleLoad {
|
||||
pub id: ModuleLoadId,
|
||||
pub root_module_id: Option<ModuleId>,
|
||||
init: LoadInit,
|
||||
root_asserted_module_type: Option<AssertedModuleType>,
|
||||
root_module_type: Option<ModuleType>,
|
||||
state: LoadState,
|
||||
module_map_rc: Rc<RefCell<ModuleMap>>,
|
||||
pending: FuturesUnordered<Pin<Box<ModuleLoadFuture>>>,
|
||||
visited: HashSet<ModuleRequest>,
|
||||
// The loader is copied from `module_map_rc`, but its reference is cloned
|
||||
// ahead of time to avoid already-borrowed errors.
|
||||
loader: Rc<dyn ModuleLoader>,
|
||||
}
|
||||
|
||||
impl RecursiveModuleLoad {
|
||||
/// Starts a new asynchronous load of the module graph for given specifier.
|
||||
///
|
||||
/// The module corresponding for the given `specifier` will be marked as
|
||||
// "the main module" (`import.meta.main` will return `true` for this module).
|
||||
fn main(specifier: &str, module_map_rc: Rc<RefCell<ModuleMap>>) -> Self {
|
||||
Self::new(LoadInit::Main(specifier.to_string()), module_map_rc)
|
||||
}
|
||||
|
||||
/// Starts a new asynchronous load of the module graph for given specifier.
|
||||
fn side(specifier: &str, module_map_rc: Rc<RefCell<ModuleMap>>) -> Self {
|
||||
Self::new(LoadInit::Side(specifier.to_string()), module_map_rc)
|
||||
}
|
||||
|
||||
/// Starts a new asynchronous load of the module graph for given specifier
|
||||
/// that was imported using `import()`.
|
||||
fn dynamic_import(
|
||||
specifier: &str,
|
||||
referrer: &str,
|
||||
asserted_module_type: AssertedModuleType,
|
||||
module_map_rc: Rc<RefCell<ModuleMap>>,
|
||||
) -> Self {
|
||||
Self::new(
|
||||
LoadInit::DynamicImport(
|
||||
specifier.to_string(),
|
||||
referrer.to_string(),
|
||||
asserted_module_type,
|
||||
),
|
||||
module_map_rc,
|
||||
)
|
||||
}
|
||||
|
||||
fn new(init: LoadInit, module_map_rc: Rc<RefCell<ModuleMap>>) -> Self {
|
||||
let id = {
|
||||
let mut module_map = module_map_rc.borrow_mut();
|
||||
let id = module_map.next_load_id;
|
||||
module_map.next_load_id += 1;
|
||||
id
|
||||
};
|
||||
let loader = module_map_rc.borrow().loader.clone();
|
||||
let asserted_module_type = match init {
|
||||
LoadInit::DynamicImport(_, _, module_type) => module_type,
|
||||
_ => AssertedModuleType::JavaScriptOrWasm,
|
||||
};
|
||||
let mut load = Self {
|
||||
id,
|
||||
root_module_id: None,
|
||||
root_asserted_module_type: None,
|
||||
root_module_type: None,
|
||||
init,
|
||||
state: LoadState::Init,
|
||||
module_map_rc: module_map_rc.clone(),
|
||||
loader,
|
||||
pending: FuturesUnordered::new(),
|
||||
visited: HashSet::new(),
|
||||
};
|
||||
// FIXME(bartlomieju): this seems fishy
|
||||
// Ignore the error here, let it be hit in `Stream::poll_next()`.
|
||||
if let Ok(root_specifier) = load.resolve_root() {
|
||||
if let Some(module_id) = module_map_rc
|
||||
.borrow()
|
||||
.get_id(root_specifier, asserted_module_type)
|
||||
{
|
||||
load.root_module_id = Some(module_id);
|
||||
load.root_asserted_module_type = Some(asserted_module_type);
|
||||
load.root_module_type = Some(
|
||||
module_map_rc
|
||||
.borrow()
|
||||
.get_info_by_id(module_id)
|
||||
.unwrap()
|
||||
.module_type,
|
||||
);
|
||||
}
|
||||
}
|
||||
load
|
||||
}
|
||||
|
||||
fn resolve_root(&self) -> Result<ModuleSpecifier, Error> {
|
||||
match self.init {
|
||||
LoadInit::Main(ref specifier) => {
|
||||
self
|
||||
.loader
|
||||
.resolve(specifier, ".", ResolutionKind::MainModule)
|
||||
}
|
||||
LoadInit::Side(ref specifier) => {
|
||||
self.loader.resolve(specifier, ".", ResolutionKind::Import)
|
||||
}
|
||||
LoadInit::DynamicImport(ref specifier, ref referrer, _) => self
|
||||
.loader
|
||||
.resolve(specifier, referrer, ResolutionKind::DynamicImport),
|
||||
}
|
||||
}
|
||||
|
||||
async fn prepare(&self) -> Result<(), Error> {
|
||||
let (module_specifier, maybe_referrer) = match self.init {
|
||||
LoadInit::Main(ref specifier) => {
|
||||
let spec =
|
||||
self
|
||||
.loader
|
||||
.resolve(specifier, ".", ResolutionKind::MainModule)?;
|
||||
(spec, None)
|
||||
}
|
||||
LoadInit::Side(ref specifier) => {
|
||||
let spec =
|
||||
self
|
||||
.loader
|
||||
.resolve(specifier, ".", ResolutionKind::Import)?;
|
||||
(spec, None)
|
||||
}
|
||||
LoadInit::DynamicImport(ref specifier, ref referrer, _) => {
|
||||
let spec = self.loader.resolve(
|
||||
specifier,
|
||||
referrer,
|
||||
ResolutionKind::DynamicImport,
|
||||
)?;
|
||||
(spec, Some(referrer.to_string()))
|
||||
}
|
||||
};
|
||||
|
||||
self
|
||||
.loader
|
||||
.prepare_load(&module_specifier, maybe_referrer, self.is_dynamic_import())
|
||||
.await
|
||||
}
|
||||
|
||||
fn is_currently_loading_main_module(&self) -> bool {
|
||||
!self.is_dynamic_import()
|
||||
&& matches!(self.init, LoadInit::Main(..))
|
||||
&& self.state == LoadState::LoadingRoot
|
||||
}
|
||||
|
||||
fn is_dynamic_import(&self) -> bool {
|
||||
matches!(self.init, LoadInit::DynamicImport(..))
|
||||
}
|
||||
|
||||
pub(crate) fn register_and_recurse(
|
||||
&mut self,
|
||||
scope: &mut v8::HandleScope,
|
||||
module_request: &ModuleRequest,
|
||||
module_source: ModuleSource,
|
||||
) -> Result<(), ModuleError> {
|
||||
let expected_asserted_module_type = module_source.module_type.into();
|
||||
let module_url_found = module_source.module_url_found;
|
||||
let module_url_specified = module_source.module_url_specified;
|
||||
|
||||
if module_request.asserted_module_type != expected_asserted_module_type {
|
||||
return Err(ModuleError::Other(generic_error(format!(
|
||||
"Expected a \"{}\" module but loaded a \"{}\" module.",
|
||||
module_request.asserted_module_type, module_source.module_type,
|
||||
))));
|
||||
}
|
||||
|
||||
// Register the module in the module map unless it's already there. If the
|
||||
// specified URL and the "true" URL are different, register the alias.
|
||||
let module_url_found = if let Some(module_url_found) = module_url_found {
|
||||
let (module_url_found1, module_url_found2) =
|
||||
module_url_found.into_cheap_copy();
|
||||
self.module_map_rc.borrow_mut().alias(
|
||||
module_url_specified,
|
||||
expected_asserted_module_type,
|
||||
module_url_found1,
|
||||
);
|
||||
module_url_found2
|
||||
} else {
|
||||
module_url_specified
|
||||
};
|
||||
|
||||
let maybe_module_id = self
|
||||
.module_map_rc
|
||||
.borrow()
|
||||
.get_id(&module_url_found, expected_asserted_module_type);
|
||||
let module_id = match maybe_module_id {
|
||||
Some(id) => {
|
||||
debug!(
|
||||
"Already-registered module fetched again: {:?}",
|
||||
module_url_found
|
||||
);
|
||||
id
|
||||
}
|
||||
None => match module_source.module_type {
|
||||
ModuleType::JavaScript => {
|
||||
self.module_map_rc.borrow_mut().new_es_module(
|
||||
scope,
|
||||
self.is_currently_loading_main_module(),
|
||||
module_url_found,
|
||||
module_source.code,
|
||||
self.is_dynamic_import(),
|
||||
)?
|
||||
}
|
||||
ModuleType::Json => self.module_map_rc.borrow_mut().new_json_module(
|
||||
scope,
|
||||
module_url_found,
|
||||
module_source.code,
|
||||
)?,
|
||||
},
|
||||
};
|
||||
|
||||
// Recurse the module's imports. There are two cases for each import:
|
||||
// 1. If the module is not in the module map, start a new load for it in
|
||||
// `self.pending`. The result of that load should eventually be passed to
|
||||
// this function for recursion.
|
||||
// 2. If the module is already in the module map, queue it up to be
|
||||
// recursed synchronously here.
|
||||
// This robustly ensures that the whole graph is in the module map before
|
||||
// `LoadState::Done` is set.
|
||||
let mut already_registered = VecDeque::new();
|
||||
already_registered.push_back((module_id, module_request.clone()));
|
||||
self.visited.insert(module_request.clone());
|
||||
while let Some((module_id, module_request)) = already_registered.pop_front()
|
||||
{
|
||||
let referrer = ModuleSpecifier::parse(&module_request.specifier).unwrap();
|
||||
let imports = self
|
||||
.module_map_rc
|
||||
.borrow()
|
||||
.get_requested_modules(module_id)
|
||||
.unwrap()
|
||||
.clone();
|
||||
for module_request in imports {
|
||||
if !self.visited.contains(&module_request) {
|
||||
if let Some(module_id) = self.module_map_rc.borrow().get_id(
|
||||
module_request.specifier.as_str(),
|
||||
module_request.asserted_module_type,
|
||||
) {
|
||||
already_registered.push_back((module_id, module_request.clone()));
|
||||
} else {
|
||||
let request = module_request.clone();
|
||||
let specifier =
|
||||
ModuleSpecifier::parse(&module_request.specifier).unwrap();
|
||||
let referrer = referrer.clone();
|
||||
let loader = self.loader.clone();
|
||||
let is_dynamic_import = self.is_dynamic_import();
|
||||
let fut = async move {
|
||||
let load_result = loader
|
||||
.load(&specifier, Some(&referrer), is_dynamic_import)
|
||||
.await;
|
||||
load_result.map(|s| (request, s))
|
||||
};
|
||||
self.pending.push(fut.boxed_local());
|
||||
}
|
||||
self.visited.insert(module_request);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update `self.state` however applicable.
|
||||
if self.state == LoadState::LoadingRoot {
|
||||
self.root_module_id = Some(module_id);
|
||||
self.root_asserted_module_type = Some(module_source.module_type.into());
|
||||
self.state = LoadState::LoadingImports;
|
||||
}
|
||||
if self.pending.is_empty() {
|
||||
self.state = LoadState::Done;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Stream for RecursiveModuleLoad {
|
||||
type Item = Result<(ModuleRequest, ModuleSource), Error>;
|
||||
|
||||
fn poll_next(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context,
|
||||
) -> Poll<Option<Self::Item>> {
|
||||
let inner = self.get_mut();
|
||||
// IMPORTANT: Do not borrow `inner.module_map_rc` here. It may not be
|
||||
// available.
|
||||
match inner.state {
|
||||
LoadState::Init => {
|
||||
let module_specifier = match inner.resolve_root() {
|
||||
Ok(url) => url,
|
||||
Err(error) => return Poll::Ready(Some(Err(error))),
|
||||
};
|
||||
let load_fut = if let Some(_module_id) = inner.root_module_id {
|
||||
// FIXME(bartlomieju): this is very bad
|
||||
// The root module is already in the module map.
|
||||
// TODO(nayeemrmn): In this case we would ideally skip to
|
||||
// `LoadState::LoadingImports` and synchronously recurse the imports
|
||||
// like the bottom of `RecursiveModuleLoad::register_and_recurse()`.
|
||||
// But the module map cannot be borrowed here. Instead fake a load
|
||||
// event so it gets passed to that function and recursed eventually.
|
||||
let asserted_module_type = inner.root_asserted_module_type.unwrap();
|
||||
let module_type = inner.root_module_type.unwrap();
|
||||
let module_request = ModuleRequest {
|
||||
specifier: module_specifier.to_string(),
|
||||
asserted_module_type,
|
||||
};
|
||||
// The code will be discarded, since this module is already in the
|
||||
// module map.
|
||||
let module_source = ModuleSource::new(
|
||||
module_type,
|
||||
Default::default(),
|
||||
&module_specifier,
|
||||
);
|
||||
futures::future::ok((module_request, module_source)).boxed()
|
||||
} else {
|
||||
let maybe_referrer = match inner.init {
|
||||
LoadInit::DynamicImport(_, ref referrer, _) => {
|
||||
resolve_url(referrer).ok()
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
let asserted_module_type = match inner.init {
|
||||
LoadInit::DynamicImport(_, _, module_type) => module_type,
|
||||
_ => AssertedModuleType::JavaScriptOrWasm,
|
||||
};
|
||||
let module_request = ModuleRequest {
|
||||
specifier: module_specifier.to_string(),
|
||||
asserted_module_type,
|
||||
};
|
||||
let loader = inner.loader.clone();
|
||||
let is_dynamic_import = inner.is_dynamic_import();
|
||||
async move {
|
||||
let result = loader
|
||||
.load(
|
||||
&module_specifier,
|
||||
maybe_referrer.as_ref(),
|
||||
is_dynamic_import,
|
||||
)
|
||||
.await;
|
||||
result.map(|s| (module_request, s))
|
||||
}
|
||||
.boxed_local()
|
||||
};
|
||||
inner.pending.push(load_fut);
|
||||
inner.state = LoadState::LoadingRoot;
|
||||
inner.try_poll_next_unpin(cx)
|
||||
}
|
||||
LoadState::LoadingRoot | LoadState::LoadingImports => {
|
||||
match inner.pending.try_poll_next_unpin(cx)? {
|
||||
Poll::Ready(None) => unreachable!(),
|
||||
Poll::Ready(Some(info)) => Poll::Ready(Some(Ok(info))),
|
||||
Poll::Pending => Poll::Pending,
|
||||
}
|
||||
}
|
||||
LoadState::Done => Poll::Ready(None),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
|
||||
#[repr(u32)]
|
||||
pub(crate) enum AssertedModuleType {
|
||||
JavaScriptOrWasm,
|
||||
Json,
|
||||
}
|
||||
|
||||
impl From<ModuleType> for AssertedModuleType {
|
||||
fn from(module_type: ModuleType) -> AssertedModuleType {
|
||||
match module_type {
|
||||
ModuleType::JavaScript => AssertedModuleType::JavaScriptOrWasm,
|
||||
ModuleType::Json => AssertedModuleType::Json,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for AssertedModuleType {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::JavaScriptOrWasm => write!(f, "JavaScriptOrWasm"),
|
||||
Self::Json => write!(f, "JSON"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Describes a request for a module as parsed from the source code.
|
||||
/// Usually executable (`JavaScriptOrWasm`) is used, except when an
|
||||
/// import assertions explicitly constrains an import to JSON, in
|
||||
/// which case this will have a `AssertedModuleType::Json`.
|
||||
#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
|
||||
pub(crate) struct ModuleRequest {
|
||||
pub specifier: String,
|
||||
pub asserted_module_type: AssertedModuleType,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub(crate) struct ModuleInfo {
|
||||
#[allow(unused)]
|
||||
pub id: ModuleId,
|
||||
// Used in "bindings.rs" for "import.meta.main" property value.
|
||||
pub main: bool,
|
||||
pub name: ModuleName,
|
||||
pub requests: Vec<ModuleRequest>,
|
||||
pub module_type: ModuleType,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum ModuleError {
|
||||
Exception(v8::Global<v8::Value>),
|
||||
Other(Error),
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -1,39 +0,0 @@
|
|||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
||||
|
||||
use std::path::Component;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
|
||||
/// Normalize all intermediate components of the path (ie. remove "./" and "../" components).
|
||||
/// Similar to `fs::canonicalize()` but doesn't resolve symlinks.
|
||||
///
|
||||
/// Taken from Cargo
|
||||
/// <https://github.com/rust-lang/cargo/blob/af307a38c20a753ec60f0ad18be5abed3db3c9ac/src/cargo/util/paths.rs#L60-L85>
|
||||
#[inline]
|
||||
pub fn normalize_path<P: AsRef<Path>>(path: P) -> PathBuf {
|
||||
let mut components = path.as_ref().components().peekable();
|
||||
let mut ret =
|
||||
if let Some(c @ Component::Prefix(..)) = components.peek().cloned() {
|
||||
components.next();
|
||||
PathBuf::from(c.as_os_str())
|
||||
} else {
|
||||
PathBuf::new()
|
||||
};
|
||||
|
||||
for component in components {
|
||||
match component {
|
||||
Component::Prefix(..) => unreachable!(),
|
||||
Component::RootDir => {
|
||||
ret.push(component.as_os_str());
|
||||
}
|
||||
Component::CurDir => {}
|
||||
Component::ParentDir => {
|
||||
ret.pop();
|
||||
}
|
||||
Component::Normal(c) => {
|
||||
ret.push(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
ret
|
||||
}
|
234
core/ops.rs
234
core/ops.rs
|
@ -1,234 +0,0 @@
|
|||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
||||
|
||||
use crate::error::AnyError;
|
||||
use crate::error::GetErrorClassFn;
|
||||
use crate::gotham_state::GothamState;
|
||||
use crate::resources::ResourceTable;
|
||||
use crate::runtime::ContextState;
|
||||
use crate::runtime::JsRuntimeState;
|
||||
use crate::OpDecl;
|
||||
use crate::OpsTracker;
|
||||
use anyhow::Error;
|
||||
use futures::task::AtomicWaker;
|
||||
use futures::Future;
|
||||
use pin_project::pin_project;
|
||||
use serde::Serialize;
|
||||
use std::cell::RefCell;
|
||||
use std::cell::UnsafeCell;
|
||||
use std::ops::Deref;
|
||||
use std::ops::DerefMut;
|
||||
use std::ptr::NonNull;
|
||||
use std::rc::Rc;
|
||||
use std::rc::Weak;
|
||||
use std::sync::Arc;
|
||||
use v8::fast_api::CFunctionInfo;
|
||||
use v8::fast_api::CTypeInfo;
|
||||
use v8::fast_api::Int64Representation;
|
||||
|
||||
pub type PromiseId = i32;
|
||||
pub type OpId = u16;
|
||||
|
||||
#[pin_project]
|
||||
pub struct OpCall<F: Future<Output = OpResult>> {
|
||||
promise_id: PromiseId,
|
||||
op_id: OpId,
|
||||
/// Future is not necessarily Unpin, so we need to pin_project.
|
||||
#[pin]
|
||||
fut: F,
|
||||
}
|
||||
|
||||
impl<F: Future<Output = OpResult>> OpCall<F> {
|
||||
/// Wraps a future; the inner future is polled the usual way (lazily).
|
||||
pub fn new(op_ctx: &OpCtx, promise_id: PromiseId, fut: F) -> Self {
|
||||
Self {
|
||||
op_id: op_ctx.id,
|
||||
promise_id,
|
||||
fut,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: Future<Output = OpResult>> Future for OpCall<F> {
|
||||
type Output = (PromiseId, OpId, OpResult);
|
||||
|
||||
fn poll(
|
||||
self: std::pin::Pin<&mut Self>,
|
||||
cx: &mut std::task::Context<'_>,
|
||||
) -> std::task::Poll<Self::Output> {
|
||||
let promise_id = self.promise_id;
|
||||
let op_id = self.op_id;
|
||||
let fut = self.project().fut;
|
||||
fut.poll(cx).map(move |res| (promise_id, op_id, res))
|
||||
}
|
||||
}
|
||||
|
||||
pub enum OpResult {
|
||||
Ok(serde_v8::SerializablePkg),
|
||||
Err(OpError),
|
||||
}
|
||||
|
||||
impl OpResult {
|
||||
pub fn to_v8<'a>(
|
||||
&mut self,
|
||||
scope: &mut v8::HandleScope<'a>,
|
||||
) -> Result<v8::Local<'a, v8::Value>, serde_v8::Error> {
|
||||
match self {
|
||||
Self::Ok(x) => x.to_v8(scope),
|
||||
Self::Err(err) => serde_v8::to_v8(scope, err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct OpError {
|
||||
#[serde(rename = "$err_class_name")]
|
||||
class_name: &'static str,
|
||||
message: String,
|
||||
code: Option<&'static str>,
|
||||
}
|
||||
|
||||
impl OpError {
|
||||
pub fn new(get_class: GetErrorClassFn, err: Error) -> Self {
|
||||
Self {
|
||||
class_name: (get_class)(&err),
|
||||
message: format!("{err:#}"),
|
||||
code: crate::error_codes::get_error_code(&err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_op_result<R: Serialize + 'static>(
|
||||
get_class: GetErrorClassFn,
|
||||
result: Result<R, Error>,
|
||||
) -> OpResult {
|
||||
match result {
|
||||
Ok(v) => OpResult::Ok(v.into()),
|
||||
Err(err) => OpResult::Err(OpError::new(get_class, err)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Per-op context.
|
||||
///
|
||||
// Note: We don't worry too much about the size of this struct because it's allocated once per realm, and is
|
||||
// stored in a contiguous array.
|
||||
pub struct OpCtx {
|
||||
pub id: OpId,
|
||||
pub state: Rc<RefCell<OpState>>,
|
||||
pub decl: Rc<OpDecl>,
|
||||
pub fast_fn_c_info: Option<NonNull<v8::fast_api::CFunctionInfo>>,
|
||||
pub runtime_state: Weak<RefCell<JsRuntimeState>>,
|
||||
pub(crate) context_state: Rc<RefCell<ContextState>>,
|
||||
/// If the last fast op failed, stores the error to be picked up by the slow op.
|
||||
pub(crate) last_fast_error: UnsafeCell<Option<AnyError>>,
|
||||
}
|
||||
|
||||
impl OpCtx {
|
||||
pub(crate) fn new(
|
||||
id: OpId,
|
||||
context_state: Rc<RefCell<ContextState>>,
|
||||
decl: Rc<OpDecl>,
|
||||
state: Rc<RefCell<OpState>>,
|
||||
runtime_state: Weak<RefCell<JsRuntimeState>>,
|
||||
) -> Self {
|
||||
let mut fast_fn_c_info = None;
|
||||
|
||||
if let Some(fast_fn) = &decl.fast_fn {
|
||||
let args = CTypeInfo::new_from_slice(fast_fn.args);
|
||||
let ret = CTypeInfo::new(fast_fn.return_type);
|
||||
|
||||
// SAFETY: all arguments are coming from the trait and they have
|
||||
// static lifetime
|
||||
let c_fn = unsafe {
|
||||
CFunctionInfo::new(
|
||||
args.as_ptr(),
|
||||
fast_fn.args.len(),
|
||||
ret.as_ptr(),
|
||||
// TODO(bartlomieju): in the future we might want to change it
|
||||
// to use BigInt representation.
|
||||
Int64Representation::Number,
|
||||
)
|
||||
};
|
||||
fast_fn_c_info = Some(c_fn);
|
||||
}
|
||||
|
||||
OpCtx {
|
||||
id,
|
||||
state,
|
||||
runtime_state,
|
||||
decl,
|
||||
context_state,
|
||||
fast_fn_c_info,
|
||||
last_fast_error: UnsafeCell::new(None),
|
||||
}
|
||||
}
|
||||
|
||||
/// This takes the last error from an [`OpCtx`], assuming that no other code anywhere
|
||||
/// can hold a `&mut` to the last_fast_error field.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// Must only be called from op implementations.
|
||||
#[inline(always)]
|
||||
pub unsafe fn unsafely_take_last_error_for_ops_only(
|
||||
&self,
|
||||
) -> Option<AnyError> {
|
||||
let opt_mut = &mut *self.last_fast_error.get();
|
||||
opt_mut.take()
|
||||
}
|
||||
|
||||
/// This set the last error for an [`OpCtx`], assuming that no other code anywhere
|
||||
/// can hold a `&mut` to the last_fast_error field.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// Must only be called from op implementations.
|
||||
#[inline(always)]
|
||||
pub unsafe fn unsafely_set_last_error_for_ops_only(&self, error: AnyError) {
|
||||
let opt_mut = &mut *self.last_fast_error.get();
|
||||
*opt_mut = Some(error);
|
||||
}
|
||||
}
|
||||
|
||||
/// Maintains the resources and ops inside a JS runtime.
|
||||
pub struct OpState {
|
||||
pub resource_table: ResourceTable,
|
||||
pub get_error_class_fn: GetErrorClassFn,
|
||||
pub tracker: OpsTracker,
|
||||
pub last_fast_op_error: Option<AnyError>,
|
||||
pub(crate) gotham_state: GothamState,
|
||||
pub waker: Arc<AtomicWaker>,
|
||||
}
|
||||
|
||||
impl OpState {
|
||||
pub fn new(ops_count: usize) -> OpState {
|
||||
OpState {
|
||||
resource_table: Default::default(),
|
||||
get_error_class_fn: &|_| "Error",
|
||||
gotham_state: Default::default(),
|
||||
last_fast_op_error: None,
|
||||
tracker: OpsTracker::new(ops_count),
|
||||
waker: Arc::new(AtomicWaker::new()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Clear all user-provided resources and state.
|
||||
pub(crate) fn clear(&mut self) {
|
||||
std::mem::take(&mut self.gotham_state);
|
||||
std::mem::take(&mut self.resource_table);
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for OpState {
|
||||
type Target = GothamState;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.gotham_state
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for OpState {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.gotham_state
|
||||
}
|
||||
}
|
|
@ -1,369 +0,0 @@
|
|||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
||||
use crate::error::format_file_name;
|
||||
use crate::error::type_error;
|
||||
use crate::io::BufMutView;
|
||||
use crate::io::BufView;
|
||||
use crate::ops_builtin_v8;
|
||||
use crate::ops_metrics::OpMetrics;
|
||||
use crate::resources::ResourceId;
|
||||
use crate::JsBuffer;
|
||||
use crate::OpState;
|
||||
use crate::Resource;
|
||||
use anyhow::Error;
|
||||
use deno_ops::op;
|
||||
use deno_ops::op2;
|
||||
use serde_v8::ToJsBuffer;
|
||||
use std::cell::RefCell;
|
||||
use std::io::stderr;
|
||||
use std::io::stdout;
|
||||
use std::io::Write;
|
||||
use std::rc::Rc;
|
||||
|
||||
crate::extension!(
|
||||
core,
|
||||
ops = [
|
||||
op_close,
|
||||
op_try_close,
|
||||
op_print,
|
||||
op_resources,
|
||||
op_wasm_streaming_feed,
|
||||
op_wasm_streaming_set_url,
|
||||
op_void_sync,
|
||||
op_error_async,
|
||||
op_error_async_deferred,
|
||||
op_void_async,
|
||||
op_void_async_deferred,
|
||||
op_add,
|
||||
op_add_async,
|
||||
// TODO(@AaronO): track IO metrics for builtin streams
|
||||
op_read,
|
||||
op_read_all,
|
||||
op_write,
|
||||
op_read_sync,
|
||||
op_write_sync,
|
||||
op_write_all,
|
||||
op_shutdown,
|
||||
op_metrics,
|
||||
op_format_file_name,
|
||||
op_is_proxy,
|
||||
op_str_byte_length,
|
||||
ops_builtin_v8::op_ref_op,
|
||||
ops_builtin_v8::op_unref_op,
|
||||
ops_builtin_v8::op_set_promise_reject_callback,
|
||||
ops_builtin_v8::op_run_microtasks,
|
||||
ops_builtin_v8::op_has_tick_scheduled,
|
||||
ops_builtin_v8::op_set_has_tick_scheduled,
|
||||
ops_builtin_v8::op_eval_context,
|
||||
ops_builtin_v8::op_queue_microtask,
|
||||
ops_builtin_v8::op_create_host_object,
|
||||
ops_builtin_v8::op_encode,
|
||||
ops_builtin_v8::op_decode,
|
||||
ops_builtin_v8::op_serialize,
|
||||
ops_builtin_v8::op_deserialize,
|
||||
ops_builtin_v8::op_set_promise_hooks,
|
||||
ops_builtin_v8::op_get_promise_details,
|
||||
ops_builtin_v8::op_get_proxy_details,
|
||||
ops_builtin_v8::op_get_non_index_property_names,
|
||||
ops_builtin_v8::op_get_constructor_name,
|
||||
ops_builtin_v8::op_memory_usage,
|
||||
ops_builtin_v8::op_set_wasm_streaming_callback,
|
||||
ops_builtin_v8::op_abort_wasm_streaming,
|
||||
ops_builtin_v8::op_destructure_error,
|
||||
ops_builtin_v8::op_dispatch_exception,
|
||||
ops_builtin_v8::op_op_names,
|
||||
ops_builtin_v8::op_apply_source_map,
|
||||
ops_builtin_v8::op_set_format_exception_callback,
|
||||
ops_builtin_v8::op_event_loop_has_more_work,
|
||||
ops_builtin_v8::op_store_pending_promise_rejection,
|
||||
ops_builtin_v8::op_remove_pending_promise_rejection,
|
||||
ops_builtin_v8::op_has_pending_promise_rejection,
|
||||
ops_builtin_v8::op_arraybuffer_was_detached,
|
||||
],
|
||||
);
|
||||
|
||||
/// Return map of resources with id as key
|
||||
/// and string representation as value.
|
||||
#[op]
|
||||
pub fn op_resources(state: &mut OpState) -> Vec<(ResourceId, String)> {
|
||||
state
|
||||
.resource_table
|
||||
.names()
|
||||
.map(|(rid, name)| (rid, name.to_string()))
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[op2(core, fast)]
|
||||
fn op_add(a: i32, b: i32) -> i32 {
|
||||
a + b
|
||||
}
|
||||
|
||||
#[op]
|
||||
pub async fn op_add_async(a: i32, b: i32) -> i32 {
|
||||
a + b
|
||||
}
|
||||
|
||||
#[op(fast)]
|
||||
pub fn op_void_sync() {}
|
||||
|
||||
#[op]
|
||||
pub async fn op_void_async() {}
|
||||
|
||||
#[op]
|
||||
pub async fn op_error_async() -> Result<(), Error> {
|
||||
Err(Error::msg("error"))
|
||||
}
|
||||
|
||||
#[op(deferred)]
|
||||
pub async fn op_error_async_deferred() -> Result<(), Error> {
|
||||
Err(Error::msg("error"))
|
||||
}
|
||||
|
||||
#[op(deferred)]
|
||||
pub async fn op_void_async_deferred() {}
|
||||
|
||||
/// Remove a resource from the resource table.
|
||||
#[op]
|
||||
pub fn op_close(
|
||||
state: &mut OpState,
|
||||
rid: Option<ResourceId>,
|
||||
) -> Result<(), Error> {
|
||||
// TODO(@AaronO): drop Option after improving type-strictness balance in
|
||||
// serde_v8
|
||||
let rid = rid.ok_or_else(|| type_error("missing or invalid `rid`"))?;
|
||||
state.resource_table.close(rid)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Try to remove a resource from the resource table. If there is no resource
|
||||
/// with the specified `rid`, this is a no-op.
|
||||
#[op]
|
||||
pub fn op_try_close(
|
||||
state: &mut OpState,
|
||||
rid: Option<ResourceId>,
|
||||
) -> Result<(), Error> {
|
||||
// TODO(@AaronO): drop Option after improving type-strictness balance in
|
||||
// serde_v8.
|
||||
let rid = rid.ok_or_else(|| type_error("missing or invalid `rid`"))?;
|
||||
let _ = state.resource_table.close(rid);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[op]
|
||||
pub fn op_metrics(state: &mut OpState) -> (OpMetrics, Vec<OpMetrics>) {
|
||||
let aggregate = state.tracker.aggregate();
|
||||
let per_op = state.tracker.per_op();
|
||||
(aggregate, per_op)
|
||||
}
|
||||
|
||||
/// Builtin utility to print to stdout/stderr
|
||||
#[op]
|
||||
pub fn op_print(msg: &str, is_err: bool) -> Result<(), Error> {
|
||||
if is_err {
|
||||
stderr().write_all(msg.as_bytes())?;
|
||||
stderr().flush().unwrap();
|
||||
} else {
|
||||
stdout().write_all(msg.as_bytes())?;
|
||||
stdout().flush().unwrap();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub struct WasmStreamingResource(pub(crate) RefCell<v8::WasmStreaming>);
|
||||
|
||||
impl Resource for WasmStreamingResource {
|
||||
fn close(self: Rc<Self>) {
|
||||
// At this point there are no clones of Rc<WasmStreamingResource> on the
|
||||
// resource table, and no one should own a reference outside of the stack.
|
||||
// Therefore, we can be sure `self` is the only reference.
|
||||
if let Ok(wsr) = Rc::try_unwrap(self) {
|
||||
wsr.0.into_inner().finish();
|
||||
} else {
|
||||
panic!("Couldn't consume WasmStreamingResource.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Feed bytes to WasmStreamingResource.
|
||||
#[op]
|
||||
pub fn op_wasm_streaming_feed(
|
||||
state: &mut OpState,
|
||||
rid: ResourceId,
|
||||
bytes: &[u8],
|
||||
) -> Result<(), Error> {
|
||||
let wasm_streaming =
|
||||
state.resource_table.get::<WasmStreamingResource>(rid)?;
|
||||
|
||||
wasm_streaming.0.borrow_mut().on_bytes_received(bytes);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[op]
|
||||
pub fn op_wasm_streaming_set_url(
|
||||
state: &mut OpState,
|
||||
rid: ResourceId,
|
||||
url: &str,
|
||||
) -> Result<(), Error> {
|
||||
let wasm_streaming =
|
||||
state.resource_table.get::<WasmStreamingResource>(rid)?;
|
||||
|
||||
wasm_streaming.0.borrow_mut().set_url(url);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[op]
|
||||
async fn op_read(
|
||||
state: Rc<RefCell<OpState>>,
|
||||
rid: ResourceId,
|
||||
buf: JsBuffer,
|
||||
) -> Result<u32, Error> {
|
||||
let resource = state.borrow().resource_table.get_any(rid)?;
|
||||
let view = BufMutView::from(buf);
|
||||
resource.read_byob(view).await.map(|(n, _)| n as u32)
|
||||
}
|
||||
|
||||
#[op]
|
||||
async fn op_read_all(
|
||||
state: Rc<RefCell<OpState>>,
|
||||
rid: ResourceId,
|
||||
) -> Result<ToJsBuffer, Error> {
|
||||
let resource = state.borrow().resource_table.get_any(rid)?;
|
||||
|
||||
// The number of bytes we attempt to grow the buffer by each time it fills
|
||||
// up and we have more data to read. We start at 64 KB. The grow_len is
|
||||
// doubled if the nread returned from a single read is equal or greater than
|
||||
// the grow_len. This allows us to reduce allocations for resources that can
|
||||
// read large chunks of data at a time.
|
||||
let mut grow_len: usize = 64 * 1024;
|
||||
|
||||
let (min, maybe_max) = resource.size_hint();
|
||||
// Try to determine an optimal starting buffer size for this resource based
|
||||
// on the size hint.
|
||||
let initial_size = match (min, maybe_max) {
|
||||
(min, Some(max)) if min == max => min as usize,
|
||||
(_min, Some(max)) if (max as usize) < grow_len => max as usize,
|
||||
(min, _) if (min as usize) < grow_len => grow_len,
|
||||
(min, _) => min as usize,
|
||||
};
|
||||
|
||||
let mut buf = BufMutView::new(initial_size);
|
||||
loop {
|
||||
// if the buffer does not have much remaining space, we may have to grow it.
|
||||
if buf.len() < grow_len {
|
||||
let vec = buf.get_mut_vec();
|
||||
match maybe_max {
|
||||
Some(max) if vec.len() >= max as usize => {
|
||||
// no need to resize the vec, because the vec is already large enough
|
||||
// to accommodate the maximum size of the read data.
|
||||
}
|
||||
Some(max) if (max as usize) < vec.len() + grow_len => {
|
||||
// grow the vec to the maximum size of the read data
|
||||
vec.resize(max as usize, 0);
|
||||
}
|
||||
_ => {
|
||||
// grow the vec by grow_len
|
||||
vec.resize(vec.len() + grow_len, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
let (n, new_buf) = resource.clone().read_byob(buf).await?;
|
||||
buf = new_buf;
|
||||
buf.advance_cursor(n);
|
||||
if n == 0 {
|
||||
break;
|
||||
}
|
||||
if n >= grow_len {
|
||||
// we managed to read more or equal data than fits in a single grow_len in
|
||||
// a single go, so let's attempt to read even more next time. this reduces
|
||||
// allocations for resources that can read large chunks of data at a time.
|
||||
grow_len *= 2;
|
||||
}
|
||||
}
|
||||
|
||||
let nread = buf.reset_cursor();
|
||||
let mut vec = buf.unwrap_vec();
|
||||
// If the buffer is larger than the amount of data read, shrink it to the
|
||||
// amount of data read.
|
||||
if nread < vec.len() {
|
||||
vec.truncate(nread);
|
||||
}
|
||||
|
||||
Ok(ToJsBuffer::from(vec))
|
||||
}
|
||||
|
||||
#[op]
|
||||
async fn op_write(
|
||||
state: Rc<RefCell<OpState>>,
|
||||
rid: ResourceId,
|
||||
buf: JsBuffer,
|
||||
) -> Result<u32, Error> {
|
||||
let resource = state.borrow().resource_table.get_any(rid)?;
|
||||
let view = BufView::from(buf);
|
||||
let resp = resource.write(view).await?;
|
||||
Ok(resp.nwritten() as u32)
|
||||
}
|
||||
|
||||
#[op(fast)]
|
||||
fn op_read_sync(
|
||||
state: &mut OpState,
|
||||
rid: ResourceId,
|
||||
data: &mut [u8],
|
||||
) -> Result<u32, Error> {
|
||||
let resource = state.resource_table.get_any(rid)?;
|
||||
resource.read_byob_sync(data).map(|n| n as u32)
|
||||
}
|
||||
|
||||
#[op]
|
||||
fn op_write_sync(
|
||||
state: &mut OpState,
|
||||
rid: ResourceId,
|
||||
data: &[u8],
|
||||
) -> Result<u32, Error> {
|
||||
let resource = state.resource_table.get_any(rid)?;
|
||||
let nwritten = resource.write_sync(data)?;
|
||||
Ok(nwritten as u32)
|
||||
}
|
||||
|
||||
#[op]
|
||||
async fn op_write_all(
|
||||
state: Rc<RefCell<OpState>>,
|
||||
rid: ResourceId,
|
||||
buf: JsBuffer,
|
||||
) -> Result<(), Error> {
|
||||
let resource = state.borrow().resource_table.get_any(rid)?;
|
||||
let view = BufView::from(buf);
|
||||
resource.write_all(view).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[op]
|
||||
async fn op_shutdown(
|
||||
state: Rc<RefCell<OpState>>,
|
||||
rid: ResourceId,
|
||||
) -> Result<(), Error> {
|
||||
let resource = state.borrow().resource_table.get_any(rid)?;
|
||||
resource.shutdown().await
|
||||
}
|
||||
|
||||
#[op]
|
||||
fn op_format_file_name(file_name: String) -> String {
|
||||
format_file_name(&file_name)
|
||||
}
|
||||
|
||||
#[op(fast)]
|
||||
fn op_is_proxy(value: serde_v8::Value) -> bool {
|
||||
value.v8_value.is_proxy()
|
||||
}
|
||||
|
||||
#[op(v8)]
|
||||
fn op_str_byte_length(
|
||||
scope: &mut v8::HandleScope,
|
||||
value: serde_v8::Value,
|
||||
) -> u32 {
|
||||
if let Ok(string) = v8::Local::<v8::String>::try_from(value.v8_value) {
|
||||
string.utf8_length(scope) as u32
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
|
@ -1,939 +0,0 @@
|
|||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
||||
use crate::error::custom_error;
|
||||
use crate::error::is_instance_of_error;
|
||||
use crate::error::range_error;
|
||||
use crate::error::type_error;
|
||||
use crate::error::JsError;
|
||||
use crate::ops_builtin::WasmStreamingResource;
|
||||
use crate::resolve_url;
|
||||
use crate::runtime::script_origin;
|
||||
use crate::serde_v8::from_v8;
|
||||
use crate::source_map::apply_source_map;
|
||||
use crate::JsBuffer;
|
||||
use crate::JsRealm;
|
||||
use crate::JsRuntime;
|
||||
use crate::ToJsBuffer;
|
||||
use anyhow::Error;
|
||||
use deno_ops::op;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
use v8::ValueDeserializerHelper;
|
||||
use v8::ValueSerializerHelper;
|
||||
|
||||
fn to_v8_fn(
|
||||
scope: &mut v8::HandleScope,
|
||||
value: serde_v8::Value,
|
||||
) -> Result<v8::Global<v8::Function>, Error> {
|
||||
v8::Local::<v8::Function>::try_from(value.v8_value)
|
||||
.map(|cb| v8::Global::new(scope, cb))
|
||||
.map_err(|err| type_error(err.to_string()))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn to_v8_local_fn(
|
||||
value: serde_v8::Value,
|
||||
) -> Result<v8::Local<v8::Function>, Error> {
|
||||
v8::Local::<v8::Function>::try_from(value.v8_value)
|
||||
.map_err(|err| type_error(err.to_string()))
|
||||
}
|
||||
|
||||
#[op(v8)]
|
||||
fn op_ref_op(scope: &mut v8::HandleScope, promise_id: i32) {
|
||||
let context_state = JsRealm::state_from_scope(scope);
|
||||
context_state.borrow_mut().unrefed_ops.remove(&promise_id);
|
||||
}
|
||||
|
||||
#[op(v8)]
|
||||
fn op_unref_op(scope: &mut v8::HandleScope, promise_id: i32) {
|
||||
let context_state = JsRealm::state_from_scope(scope);
|
||||
context_state.borrow_mut().unrefed_ops.insert(promise_id);
|
||||
}
|
||||
|
||||
#[op(v8)]
|
||||
fn op_set_promise_reject_callback<'a>(
|
||||
scope: &mut v8::HandleScope<'a>,
|
||||
cb: serde_v8::Value,
|
||||
) -> Result<Option<serde_v8::Value<'a>>, Error> {
|
||||
let cb = to_v8_fn(scope, cb)?;
|
||||
let context_state_rc = JsRealm::state_from_scope(scope);
|
||||
let old = context_state_rc
|
||||
.borrow_mut()
|
||||
.js_promise_reject_cb
|
||||
.replace(Rc::new(cb));
|
||||
let old = old.map(|v| v8::Local::new(scope, &*v));
|
||||
Ok(old.map(|v| from_v8(scope, v.into()).unwrap()))
|
||||
}
|
||||
|
||||
#[op(v8)]
|
||||
fn op_run_microtasks(scope: &mut v8::HandleScope) {
|
||||
scope.perform_microtask_checkpoint();
|
||||
}
|
||||
|
||||
#[op(v8)]
|
||||
fn op_has_tick_scheduled(scope: &mut v8::HandleScope) -> bool {
|
||||
let state_rc = JsRuntime::state_from(scope);
|
||||
let state = state_rc.borrow();
|
||||
state.has_tick_scheduled
|
||||
}
|
||||
|
||||
#[op(v8)]
|
||||
fn op_set_has_tick_scheduled(scope: &mut v8::HandleScope, v: bool) {
|
||||
let state_rc = JsRuntime::state_from(scope);
|
||||
state_rc.borrow_mut().has_tick_scheduled = v;
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct EvalContextError<'s> {
|
||||
thrown: serde_v8::Value<'s>,
|
||||
is_native_error: bool,
|
||||
is_compile_error: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct EvalContextResult<'s>(
|
||||
Option<serde_v8::Value<'s>>,
|
||||
Option<EvalContextError<'s>>,
|
||||
);
|
||||
|
||||
#[op(v8)]
|
||||
fn op_eval_context<'a>(
|
||||
scope: &mut v8::HandleScope<'a>,
|
||||
source: serde_v8::Value<'a>,
|
||||
specifier: String,
|
||||
) -> Result<EvalContextResult<'a>, Error> {
|
||||
let tc_scope = &mut v8::TryCatch::new(scope);
|
||||
let source = v8::Local::<v8::String>::try_from(source.v8_value)
|
||||
.map_err(|_| type_error("Invalid source"))?;
|
||||
let specifier = resolve_url(&specifier)?.to_string();
|
||||
let specifier = v8::String::new(tc_scope, &specifier).unwrap();
|
||||
let origin = script_origin(tc_scope, specifier);
|
||||
|
||||
let script = match v8::Script::compile(tc_scope, source, Some(&origin)) {
|
||||
Some(s) => s,
|
||||
None => {
|
||||
assert!(tc_scope.has_caught());
|
||||
let exception = tc_scope.exception().unwrap();
|
||||
return Ok(EvalContextResult(
|
||||
None,
|
||||
Some(EvalContextError {
|
||||
thrown: exception.into(),
|
||||
is_native_error: is_instance_of_error(tc_scope, exception),
|
||||
is_compile_error: true,
|
||||
}),
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
match script.run(tc_scope) {
|
||||
Some(result) => Ok(EvalContextResult(Some(result.into()), None)),
|
||||
None => {
|
||||
assert!(tc_scope.has_caught());
|
||||
let exception = tc_scope.exception().unwrap();
|
||||
Ok(EvalContextResult(
|
||||
None,
|
||||
Some(EvalContextError {
|
||||
thrown: exception.into(),
|
||||
is_native_error: is_instance_of_error(tc_scope, exception),
|
||||
is_compile_error: false,
|
||||
}),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[op(v8)]
|
||||
fn op_queue_microtask(
|
||||
scope: &mut v8::HandleScope,
|
||||
cb: serde_v8::Value,
|
||||
) -> Result<(), Error> {
|
||||
scope.enqueue_microtask(to_v8_local_fn(cb)?);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[op(v8)]
|
||||
fn op_create_host_object<'a>(
|
||||
scope: &mut v8::HandleScope<'a>,
|
||||
) -> serde_v8::Value<'a> {
|
||||
let template = v8::ObjectTemplate::new(scope);
|
||||
template.set_internal_field_count(1);
|
||||
let object = template.new_instance(scope).unwrap();
|
||||
from_v8(scope, object.into()).unwrap()
|
||||
}
|
||||
|
||||
#[op(v8)]
|
||||
fn op_encode<'a>(
|
||||
scope: &mut v8::HandleScope<'a>,
|
||||
text: serde_v8::Value<'a>,
|
||||
) -> Result<serde_v8::Value<'a>, Error> {
|
||||
let text = v8::Local::<v8::String>::try_from(text.v8_value)
|
||||
.map_err(|_| type_error("Invalid argument"))?;
|
||||
let text_str = serde_v8::to_utf8(text, scope);
|
||||
let bytes = text_str.into_bytes();
|
||||
let len = bytes.len();
|
||||
let backing_store =
|
||||
v8::ArrayBuffer::new_backing_store_from_vec(bytes).make_shared();
|
||||
let buffer = v8::ArrayBuffer::with_backing_store(scope, &backing_store);
|
||||
let u8array = v8::Uint8Array::new(scope, buffer, 0, len).unwrap();
|
||||
Ok((from_v8(scope, u8array.into()))?)
|
||||
}
|
||||
|
||||
#[op(v8)]
|
||||
fn op_decode<'a>(
|
||||
scope: &mut v8::HandleScope<'a>,
|
||||
zero_copy: &[u8],
|
||||
) -> Result<serde_v8::Value<'a>, Error> {
|
||||
let buf = &zero_copy;
|
||||
|
||||
// Strip BOM
|
||||
let buf =
|
||||
if buf.len() >= 3 && buf[0] == 0xef && buf[1] == 0xbb && buf[2] == 0xbf {
|
||||
&buf[3..]
|
||||
} else {
|
||||
buf
|
||||
};
|
||||
|
||||
// If `String::new_from_utf8()` returns `None`, this means that the
|
||||
// length of the decoded string would be longer than what V8 can
|
||||
// handle. In this case we return `RangeError`.
|
||||
//
|
||||
// For more details see:
|
||||
// - https://encoding.spec.whatwg.org/#dom-textdecoder-decode
|
||||
// - https://github.com/denoland/deno/issues/6649
|
||||
// - https://github.com/v8/v8/blob/d68fb4733e39525f9ff0a9222107c02c28096e2a/include/v8.h#L3277-L3278
|
||||
match v8::String::new_from_utf8(scope, buf, v8::NewStringType::Normal) {
|
||||
Some(text) => Ok(from_v8(scope, text.into())?),
|
||||
None => Err(range_error("string too long")),
|
||||
}
|
||||
}
|
||||
|
||||
struct SerializeDeserialize<'a> {
|
||||
host_objects: Option<v8::Local<'a, v8::Array>>,
|
||||
error_callback: Option<v8::Local<'a, v8::Function>>,
|
||||
for_storage: bool,
|
||||
}
|
||||
|
||||
impl<'a> v8::ValueSerializerImpl for SerializeDeserialize<'a> {
|
||||
#[allow(unused_variables)]
|
||||
fn throw_data_clone_error<'s>(
|
||||
&mut self,
|
||||
scope: &mut v8::HandleScope<'s>,
|
||||
message: v8::Local<'s, v8::String>,
|
||||
) {
|
||||
if let Some(cb) = self.error_callback {
|
||||
let scope = &mut v8::TryCatch::new(scope);
|
||||
let undefined = v8::undefined(scope).into();
|
||||
cb.call(scope, undefined, &[message.into()]);
|
||||
if scope.has_caught() || scope.has_terminated() {
|
||||
scope.rethrow();
|
||||
return;
|
||||
};
|
||||
}
|
||||
let error = v8::Exception::type_error(scope, message);
|
||||
scope.throw_exception(error);
|
||||
}
|
||||
|
||||
fn get_shared_array_buffer_id<'s>(
|
||||
&mut self,
|
||||
scope: &mut v8::HandleScope<'s>,
|
||||
shared_array_buffer: v8::Local<'s, v8::SharedArrayBuffer>,
|
||||
) -> Option<u32> {
|
||||
if self.for_storage {
|
||||
return None;
|
||||
}
|
||||
let state_rc = JsRuntime::state_from(scope);
|
||||
let state = state_rc.borrow_mut();
|
||||
if let Some(shared_array_buffer_store) = &state.shared_array_buffer_store {
|
||||
let backing_store = shared_array_buffer.get_backing_store();
|
||||
let id = shared_array_buffer_store.insert(backing_store);
|
||||
Some(id)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn get_wasm_module_transfer_id(
|
||||
&mut self,
|
||||
scope: &mut v8::HandleScope<'_>,
|
||||
module: v8::Local<v8::WasmModuleObject>,
|
||||
) -> Option<u32> {
|
||||
if self.for_storage {
|
||||
let message = v8::String::new(scope, "Wasm modules cannot be stored")?;
|
||||
self.throw_data_clone_error(scope, message);
|
||||
return None;
|
||||
}
|
||||
let state_rc = JsRuntime::state_from(scope);
|
||||
let state = state_rc.borrow_mut();
|
||||
if let Some(compiled_wasm_module_store) = &state.compiled_wasm_module_store
|
||||
{
|
||||
let compiled_wasm_module = module.get_compiled_module();
|
||||
let id = compiled_wasm_module_store.insert(compiled_wasm_module);
|
||||
Some(id)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn write_host_object<'s>(
|
||||
&mut self,
|
||||
scope: &mut v8::HandleScope<'s>,
|
||||
object: v8::Local<'s, v8::Object>,
|
||||
value_serializer: &mut dyn v8::ValueSerializerHelper,
|
||||
) -> Option<bool> {
|
||||
if let Some(host_objects) = self.host_objects {
|
||||
for i in 0..host_objects.length() {
|
||||
let value = host_objects.get_index(scope, i).unwrap();
|
||||
if value == object {
|
||||
value_serializer.write_uint32(i);
|
||||
return Some(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
let message = v8::String::new(scope, "Unsupported object type").unwrap();
|
||||
self.throw_data_clone_error(scope, message);
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> v8::ValueDeserializerImpl for SerializeDeserialize<'a> {
|
||||
fn get_shared_array_buffer_from_id<'s>(
|
||||
&mut self,
|
||||
scope: &mut v8::HandleScope<'s>,
|
||||
transfer_id: u32,
|
||||
) -> Option<v8::Local<'s, v8::SharedArrayBuffer>> {
|
||||
if self.for_storage {
|
||||
return None;
|
||||
}
|
||||
let state_rc = JsRuntime::state_from(scope);
|
||||
let state = state_rc.borrow_mut();
|
||||
if let Some(shared_array_buffer_store) = &state.shared_array_buffer_store {
|
||||
let backing_store = shared_array_buffer_store.take(transfer_id)?;
|
||||
let shared_array_buffer =
|
||||
v8::SharedArrayBuffer::with_backing_store(scope, &backing_store);
|
||||
Some(shared_array_buffer)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn get_wasm_module_from_id<'s>(
|
||||
&mut self,
|
||||
scope: &mut v8::HandleScope<'s>,
|
||||
clone_id: u32,
|
||||
) -> Option<v8::Local<'s, v8::WasmModuleObject>> {
|
||||
if self.for_storage {
|
||||
return None;
|
||||
}
|
||||
let state_rc = JsRuntime::state_from(scope);
|
||||
let state = state_rc.borrow_mut();
|
||||
if let Some(compiled_wasm_module_store) = &state.compiled_wasm_module_store
|
||||
{
|
||||
let compiled_module = compiled_wasm_module_store.take(clone_id)?;
|
||||
v8::WasmModuleObject::from_compiled_module(scope, &compiled_module)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn read_host_object<'s>(
|
||||
&mut self,
|
||||
scope: &mut v8::HandleScope<'s>,
|
||||
value_deserializer: &mut dyn v8::ValueDeserializerHelper,
|
||||
) -> Option<v8::Local<'s, v8::Object>> {
|
||||
if let Some(host_objects) = self.host_objects {
|
||||
let mut i = 0;
|
||||
if !value_deserializer.read_uint32(&mut i) {
|
||||
return None;
|
||||
}
|
||||
let maybe_value = host_objects.get_index(scope, i);
|
||||
if let Some(value) = maybe_value {
|
||||
return value.to_object(scope);
|
||||
}
|
||||
}
|
||||
|
||||
let message: v8::Local<v8::String> =
|
||||
v8::String::new(scope, "Failed to deserialize host object").unwrap();
|
||||
let error = v8::Exception::error(scope, message);
|
||||
scope.throw_exception(error);
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct SerializeDeserializeOptions<'a> {
|
||||
host_objects: Option<serde_v8::Value<'a>>,
|
||||
transferred_array_buffers: Option<serde_v8::Value<'a>>,
|
||||
#[serde(default)]
|
||||
for_storage: bool,
|
||||
}
|
||||
|
||||
#[op(v8)]
|
||||
fn op_serialize(
|
||||
scope: &mut v8::HandleScope,
|
||||
value: serde_v8::Value,
|
||||
options: Option<SerializeDeserializeOptions>,
|
||||
error_callback: Option<serde_v8::Value>,
|
||||
) -> Result<ToJsBuffer, Error> {
|
||||
let options = options.unwrap_or_default();
|
||||
let error_callback = match error_callback {
|
||||
Some(cb) => Some(
|
||||
v8::Local::<v8::Function>::try_from(cb.v8_value)
|
||||
.map_err(|_| type_error("Invalid error callback"))?,
|
||||
),
|
||||
None => None,
|
||||
};
|
||||
let host_objects = match options.host_objects {
|
||||
Some(value) => Some(
|
||||
v8::Local::<v8::Array>::try_from(value.v8_value)
|
||||
.map_err(|_| type_error("hostObjects not an array"))?,
|
||||
),
|
||||
None => None,
|
||||
};
|
||||
let transferred_array_buffers = match options.transferred_array_buffers {
|
||||
Some(value) => Some(
|
||||
v8::Local::<v8::Array>::try_from(value.v8_value)
|
||||
.map_err(|_| type_error("transferredArrayBuffers not an array"))?,
|
||||
),
|
||||
None => None,
|
||||
};
|
||||
|
||||
let serialize_deserialize = Box::new(SerializeDeserialize {
|
||||
host_objects,
|
||||
error_callback,
|
||||
for_storage: options.for_storage,
|
||||
});
|
||||
let mut value_serializer =
|
||||
v8::ValueSerializer::new(scope, serialize_deserialize);
|
||||
value_serializer.write_header();
|
||||
|
||||
if let Some(transferred_array_buffers) = transferred_array_buffers {
|
||||
let state_rc = JsRuntime::state_from(scope);
|
||||
let state = state_rc.borrow_mut();
|
||||
for index in 0..transferred_array_buffers.length() {
|
||||
let i = v8::Number::new(scope, index as f64).into();
|
||||
let buf = transferred_array_buffers.get(scope, i).unwrap();
|
||||
let buf = v8::Local::<v8::ArrayBuffer>::try_from(buf).map_err(|_| {
|
||||
type_error("item in transferredArrayBuffers not an ArrayBuffer")
|
||||
})?;
|
||||
if let Some(shared_array_buffer_store) = &state.shared_array_buffer_store
|
||||
{
|
||||
if !buf.is_detachable() {
|
||||
return Err(type_error(
|
||||
"item in transferredArrayBuffers is not transferable",
|
||||
));
|
||||
}
|
||||
|
||||
if buf.was_detached() {
|
||||
return Err(custom_error(
|
||||
"DOMExceptionOperationError",
|
||||
format!("ArrayBuffer at index {index} is already detached"),
|
||||
));
|
||||
}
|
||||
|
||||
let backing_store = buf.get_backing_store();
|
||||
buf.detach(None);
|
||||
let id = shared_array_buffer_store.insert(backing_store);
|
||||
value_serializer.transfer_array_buffer(id, buf);
|
||||
let id = v8::Number::new(scope, id as f64).into();
|
||||
transferred_array_buffers.set(scope, i, id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let scope = &mut v8::TryCatch::new(scope);
|
||||
let ret =
|
||||
value_serializer.write_value(scope.get_current_context(), value.v8_value);
|
||||
if scope.has_caught() || scope.has_terminated() {
|
||||
scope.rethrow();
|
||||
// Dummy value, this result will be discarded because an error was thrown.
|
||||
Ok(ToJsBuffer::empty())
|
||||
} else if let Some(true) = ret {
|
||||
let vector = value_serializer.release();
|
||||
Ok(vector.into())
|
||||
} else {
|
||||
Err(type_error("Failed to serialize response"))
|
||||
}
|
||||
}
|
||||
|
||||
#[op(v8)]
|
||||
fn op_deserialize<'a>(
|
||||
scope: &mut v8::HandleScope<'a>,
|
||||
zero_copy: JsBuffer,
|
||||
options: Option<SerializeDeserializeOptions>,
|
||||
) -> Result<serde_v8::Value<'a>, Error> {
|
||||
let options = options.unwrap_or_default();
|
||||
let host_objects = match options.host_objects {
|
||||
Some(value) => Some(
|
||||
v8::Local::<v8::Array>::try_from(value.v8_value)
|
||||
.map_err(|_| type_error("hostObjects not an array"))?,
|
||||
),
|
||||
None => None,
|
||||
};
|
||||
let transferred_array_buffers = match options.transferred_array_buffers {
|
||||
Some(value) => Some(
|
||||
v8::Local::<v8::Array>::try_from(value.v8_value)
|
||||
.map_err(|_| type_error("transferredArrayBuffers not an array"))?,
|
||||
),
|
||||
None => None,
|
||||
};
|
||||
|
||||
let serialize_deserialize = Box::new(SerializeDeserialize {
|
||||
host_objects,
|
||||
error_callback: None,
|
||||
for_storage: options.for_storage,
|
||||
});
|
||||
let mut value_deserializer =
|
||||
v8::ValueDeserializer::new(scope, serialize_deserialize, &zero_copy);
|
||||
let parsed_header = value_deserializer
|
||||
.read_header(scope.get_current_context())
|
||||
.unwrap_or_default();
|
||||
if !parsed_header {
|
||||
return Err(range_error("could not deserialize value"));
|
||||
}
|
||||
|
||||
if let Some(transferred_array_buffers) = transferred_array_buffers {
|
||||
let state_rc = JsRuntime::state_from(scope);
|
||||
let state = state_rc.borrow_mut();
|
||||
if let Some(shared_array_buffer_store) = &state.shared_array_buffer_store {
|
||||
for i in 0..transferred_array_buffers.length() {
|
||||
let i = v8::Number::new(scope, i as f64).into();
|
||||
let id_val = transferred_array_buffers.get(scope, i).unwrap();
|
||||
let id = match id_val.number_value(scope) {
|
||||
Some(id) => id as u32,
|
||||
None => {
|
||||
return Err(type_error(
|
||||
"item in transferredArrayBuffers not number",
|
||||
))
|
||||
}
|
||||
};
|
||||
if let Some(backing_store) = shared_array_buffer_store.take(id) {
|
||||
let array_buffer =
|
||||
v8::ArrayBuffer::with_backing_store(scope, &backing_store);
|
||||
value_deserializer.transfer_array_buffer(id, array_buffer);
|
||||
transferred_array_buffers.set(scope, id_val, array_buffer.into());
|
||||
} else {
|
||||
return Err(type_error(
|
||||
"transferred array buffer not present in shared_array_buffer_store",
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let value = value_deserializer.read_value(scope.get_current_context());
|
||||
match value {
|
||||
Some(deserialized) => Ok(deserialized.into()),
|
||||
None => Err(range_error("could not deserialize value")),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct PromiseDetails<'s>(u32, Option<serde_v8::Value<'s>>);
|
||||
|
||||
#[op(v8)]
|
||||
fn op_get_promise_details<'a>(
|
||||
scope: &mut v8::HandleScope<'a>,
|
||||
promise: serde_v8::Value<'a>,
|
||||
) -> Result<PromiseDetails<'a>, Error> {
|
||||
let promise = v8::Local::<v8::Promise>::try_from(promise.v8_value)
|
||||
.map_err(|_| type_error("Invalid argument"))?;
|
||||
match promise.state() {
|
||||
v8::PromiseState::Pending => Ok(PromiseDetails(0, None)),
|
||||
v8::PromiseState::Fulfilled => {
|
||||
Ok(PromiseDetails(1, Some(promise.result(scope).into())))
|
||||
}
|
||||
v8::PromiseState::Rejected => {
|
||||
Ok(PromiseDetails(2, Some(promise.result(scope).into())))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[op(v8)]
|
||||
fn op_set_promise_hooks(
|
||||
scope: &mut v8::HandleScope,
|
||||
init_hook: serde_v8::Value,
|
||||
before_hook: serde_v8::Value,
|
||||
after_hook: serde_v8::Value,
|
||||
resolve_hook: serde_v8::Value,
|
||||
) -> Result<(), Error> {
|
||||
let v8_fns = [init_hook, before_hook, after_hook, resolve_hook]
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.filter(|(_, hook)| !hook.v8_value.is_undefined())
|
||||
.try_fold([None; 4], |mut v8_fns, (i, hook)| {
|
||||
let v8_fn = v8::Local::<v8::Function>::try_from(hook.v8_value)
|
||||
.map_err(|err| type_error(err.to_string()))?;
|
||||
v8_fns[i] = Some(v8_fn);
|
||||
Ok::<_, Error>(v8_fns)
|
||||
})?;
|
||||
|
||||
scope.set_promise_hooks(
|
||||
v8_fns[0], // init
|
||||
v8_fns[1], // before
|
||||
v8_fns[2], // after
|
||||
v8_fns[3], // resolve
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Based on https://github.com/nodejs/node/blob/1e470510ff74391d7d4ec382909ea8960d2d2fbc/src/node_util.cc
|
||||
// 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.
|
||||
#[op(v8)]
|
||||
fn op_get_proxy_details<'a>(
|
||||
scope: &mut v8::HandleScope<'a>,
|
||||
proxy: serde_v8::Value<'a>,
|
||||
) -> Option<(serde_v8::Value<'a>, serde_v8::Value<'a>)> {
|
||||
let proxy = match v8::Local::<v8::Proxy>::try_from(proxy.v8_value) {
|
||||
Ok(proxy) => proxy,
|
||||
Err(_) => return None,
|
||||
};
|
||||
let target = proxy.get_target(scope);
|
||||
let handler = proxy.get_handler(scope);
|
||||
Some((target.into(), handler.into()))
|
||||
}
|
||||
|
||||
#[op(v8)]
|
||||
fn op_get_non_index_property_names<'a>(
|
||||
scope: &mut v8::HandleScope<'a>,
|
||||
obj: serde_v8::Value<'a>,
|
||||
filter: u32,
|
||||
) -> Option<serde_v8::Value<'a>> {
|
||||
let obj = match v8::Local::<v8::Object>::try_from(obj.v8_value) {
|
||||
Ok(proxy) => proxy,
|
||||
Err(_) => return None,
|
||||
};
|
||||
|
||||
let mut property_filter = v8::PropertyFilter::ALL_PROPERTIES;
|
||||
if filter & 1 == 1 {
|
||||
property_filter = property_filter | v8::PropertyFilter::ONLY_WRITABLE
|
||||
}
|
||||
if filter & 2 == 2 {
|
||||
property_filter = property_filter | v8::PropertyFilter::ONLY_ENUMERABLE
|
||||
}
|
||||
if filter & 4 == 4 {
|
||||
property_filter = property_filter | v8::PropertyFilter::ONLY_CONFIGURABLE
|
||||
}
|
||||
if filter & 8 == 8 {
|
||||
property_filter = property_filter | v8::PropertyFilter::SKIP_STRINGS
|
||||
}
|
||||
if filter & 16 == 16 {
|
||||
property_filter = property_filter | v8::PropertyFilter::SKIP_SYMBOLS
|
||||
}
|
||||
|
||||
let maybe_names = obj.get_property_names(
|
||||
scope,
|
||||
v8::GetPropertyNamesArgs {
|
||||
mode: v8::KeyCollectionMode::OwnOnly,
|
||||
property_filter,
|
||||
index_filter: v8::IndexFilter::SkipIndices,
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
|
||||
if let Some(names) = maybe_names {
|
||||
let names_val: v8::Local<v8::Value> = names.into();
|
||||
Some(names_val.into())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[op(v8)]
|
||||
fn op_get_constructor_name<'a>(
|
||||
scope: &mut v8::HandleScope<'a>,
|
||||
obj: serde_v8::Value<'a>,
|
||||
) -> Option<String> {
|
||||
let obj = match v8::Local::<v8::Object>::try_from(obj.v8_value) {
|
||||
Ok(proxy) => proxy,
|
||||
Err(_) => return None,
|
||||
};
|
||||
|
||||
let name = obj.get_constructor_name().to_rust_string_lossy(scope);
|
||||
Some(name)
|
||||
}
|
||||
|
||||
// HeapStats stores values from a isolate.get_heap_statistics() call
|
||||
#[derive(Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct MemoryUsage {
|
||||
physical_total: usize,
|
||||
heap_total: usize,
|
||||
heap_used: usize,
|
||||
external: usize,
|
||||
// TODO: track ArrayBuffers, would require using a custom allocator to track
|
||||
// but it's otherwise a subset of external so can be indirectly tracked
|
||||
// array_buffers: usize,
|
||||
}
|
||||
|
||||
#[op(v8)]
|
||||
fn op_memory_usage(scope: &mut v8::HandleScope) -> MemoryUsage {
|
||||
let mut s = v8::HeapStatistics::default();
|
||||
scope.get_heap_statistics(&mut s);
|
||||
MemoryUsage {
|
||||
physical_total: s.total_physical_size(),
|
||||
heap_total: s.total_heap_size(),
|
||||
heap_used: s.used_heap_size(),
|
||||
external: s.external_memory(),
|
||||
}
|
||||
}
|
||||
|
||||
#[op(v8)]
|
||||
fn op_set_wasm_streaming_callback(
|
||||
scope: &mut v8::HandleScope,
|
||||
cb: serde_v8::Value,
|
||||
) -> Result<(), Error> {
|
||||
let cb = to_v8_fn(scope, cb)?;
|
||||
let context_state_rc = JsRealm::state_from_scope(scope);
|
||||
let mut context_state = context_state_rc.borrow_mut();
|
||||
// The callback to pass to the v8 API has to be a unit type, so it can't
|
||||
// borrow or move any local variables. Therefore, we're storing the JS
|
||||
// callback in a JsRuntimeState slot.
|
||||
if context_state.js_wasm_streaming_cb.is_some() {
|
||||
return Err(type_error("op_set_wasm_streaming_callback already called"));
|
||||
}
|
||||
context_state.js_wasm_streaming_cb = Some(Rc::new(cb));
|
||||
|
||||
scope.set_wasm_streaming_callback(|scope, arg, wasm_streaming| {
|
||||
let (cb_handle, streaming_rid) = {
|
||||
let context_state_rc = JsRealm::state_from_scope(scope);
|
||||
let cb_handle = context_state_rc
|
||||
.borrow()
|
||||
.js_wasm_streaming_cb
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.clone();
|
||||
let state_rc = JsRuntime::state_from(scope);
|
||||
let streaming_rid = state_rc
|
||||
.borrow()
|
||||
.op_state
|
||||
.borrow_mut()
|
||||
.resource_table
|
||||
.add(WasmStreamingResource(RefCell::new(wasm_streaming)));
|
||||
(cb_handle, streaming_rid)
|
||||
};
|
||||
|
||||
let undefined = v8::undefined(scope);
|
||||
let rid = serde_v8::to_v8(scope, streaming_rid).unwrap();
|
||||
cb_handle
|
||||
.open(scope)
|
||||
.call(scope, undefined.into(), &[arg, rid]);
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(clippy::let_and_return)]
|
||||
#[op(v8)]
|
||||
fn op_abort_wasm_streaming(
|
||||
scope: &mut v8::HandleScope,
|
||||
rid: u32,
|
||||
error: serde_v8::Value,
|
||||
) -> Result<(), Error> {
|
||||
let wasm_streaming = {
|
||||
let state_rc = JsRuntime::state_from(scope);
|
||||
let state = state_rc.borrow();
|
||||
let wsr = state
|
||||
.op_state
|
||||
.borrow_mut()
|
||||
.resource_table
|
||||
.take::<WasmStreamingResource>(rid)?;
|
||||
wsr
|
||||
};
|
||||
|
||||
// At this point there are no clones of Rc<WasmStreamingResource> on the
|
||||
// resource table, and no one should own a reference because we're never
|
||||
// cloning them. So we can be sure `wasm_streaming` is the only reference.
|
||||
if let Ok(wsr) = std::rc::Rc::try_unwrap(wasm_streaming) {
|
||||
// NOTE: v8::WasmStreaming::abort can't be called while `state` is borrowed;
|
||||
// see https://github.com/denoland/deno/issues/13917
|
||||
wsr.0.into_inner().abort(Some(error.v8_value));
|
||||
} else {
|
||||
panic!("Couldn't consume WasmStreamingResource.");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[op(v8)]
|
||||
fn op_destructure_error(
|
||||
scope: &mut v8::HandleScope,
|
||||
error: serde_v8::Value,
|
||||
) -> JsError {
|
||||
JsError::from_v8_exception(scope, error.v8_value)
|
||||
}
|
||||
|
||||
/// Effectively throw an uncatchable error. This will terminate runtime
|
||||
/// execution before any more JS code can run, except in the REPL where it
|
||||
/// should just output the error to the console.
|
||||
#[op(v8)]
|
||||
fn op_dispatch_exception(
|
||||
scope: &mut v8::HandleScope,
|
||||
exception: serde_v8::Value,
|
||||
) {
|
||||
let state_rc = JsRuntime::state_from(scope);
|
||||
let mut state = state_rc.borrow_mut();
|
||||
if let Some(inspector) = &state.inspector {
|
||||
let inspector = inspector.borrow();
|
||||
inspector.exception_thrown(scope, exception.v8_value, false);
|
||||
// This indicates that the op is being called from a REPL. Skip termination.
|
||||
if inspector.is_dispatching_message() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
state.dispatched_exception = Some(v8::Global::new(scope, exception.v8_value));
|
||||
scope.terminate_execution();
|
||||
}
|
||||
|
||||
#[op(v8)]
|
||||
fn op_op_names(scope: &mut v8::HandleScope) -> Vec<String> {
|
||||
let state_rc = JsRealm::state_from_scope(scope);
|
||||
let state = state_rc.borrow();
|
||||
state
|
||||
.op_ctxs
|
||||
.iter()
|
||||
.map(|o| o.decl.name.to_string())
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct Location {
|
||||
file_name: String,
|
||||
line_number: u32,
|
||||
column_number: u32,
|
||||
}
|
||||
|
||||
#[op(v8)]
|
||||
fn op_apply_source_map(
|
||||
scope: &mut v8::HandleScope,
|
||||
location: Location,
|
||||
) -> Result<Location, Error> {
|
||||
let state_rc = JsRuntime::state_from(scope);
|
||||
let (getter, cache) = {
|
||||
let state = state_rc.borrow();
|
||||
(
|
||||
state.source_map_getter.clone(),
|
||||
state.source_map_cache.clone(),
|
||||
)
|
||||
};
|
||||
|
||||
if let Some(source_map_getter) = getter {
|
||||
let mut cache = cache.borrow_mut();
|
||||
let mut location = location;
|
||||
let (f, l, c) = apply_source_map(
|
||||
location.file_name,
|
||||
location.line_number.into(),
|
||||
location.column_number.into(),
|
||||
&mut cache,
|
||||
&**source_map_getter,
|
||||
);
|
||||
location.file_name = f;
|
||||
location.line_number = l as u32;
|
||||
location.column_number = c as u32;
|
||||
Ok(location)
|
||||
} else {
|
||||
Ok(location)
|
||||
}
|
||||
}
|
||||
|
||||
/// Set a callback which formats exception messages as stored in
|
||||
/// `JsError::exception_message`. The callback is passed the error value and
|
||||
/// should return a string or `null`. If no callback is set or the callback
|
||||
/// returns `null`, the built-in default formatting will be used.
|
||||
#[op(v8)]
|
||||
fn op_set_format_exception_callback<'a>(
|
||||
scope: &mut v8::HandleScope<'a>,
|
||||
cb: serde_v8::Value<'a>,
|
||||
) -> Result<Option<serde_v8::Value<'a>>, Error> {
|
||||
let cb = to_v8_fn(scope, cb)?;
|
||||
let context_state_rc = JsRealm::state_from_scope(scope);
|
||||
let old = context_state_rc
|
||||
.borrow_mut()
|
||||
.js_format_exception_cb
|
||||
.replace(Rc::new(cb));
|
||||
let old = old.map(|v| v8::Local::new(scope, &*v));
|
||||
Ok(old.map(|v| from_v8(scope, v.into()).unwrap()))
|
||||
}
|
||||
|
||||
#[op(v8)]
|
||||
fn op_event_loop_has_more_work(scope: &mut v8::HandleScope) -> bool {
|
||||
JsRuntime::event_loop_pending_state_from_scope(scope).is_pending()
|
||||
}
|
||||
|
||||
#[op(v8)]
|
||||
fn op_store_pending_promise_rejection<'a>(
|
||||
scope: &mut v8::HandleScope<'a>,
|
||||
promise: serde_v8::Value<'a>,
|
||||
reason: serde_v8::Value<'a>,
|
||||
) {
|
||||
let context_state_rc = JsRealm::state_from_scope(scope);
|
||||
let mut context_state = context_state_rc.borrow_mut();
|
||||
let promise_value =
|
||||
v8::Local::<v8::Promise>::try_from(promise.v8_value).unwrap();
|
||||
let promise_global = v8::Global::new(scope, promise_value);
|
||||
let error_global = v8::Global::new(scope, reason.v8_value);
|
||||
context_state
|
||||
.pending_promise_rejections
|
||||
.push_back((promise_global, error_global));
|
||||
}
|
||||
|
||||
#[op(v8)]
|
||||
fn op_remove_pending_promise_rejection<'a>(
|
||||
scope: &mut v8::HandleScope<'a>,
|
||||
promise: serde_v8::Value<'a>,
|
||||
) {
|
||||
let context_state_rc = JsRealm::state_from_scope(scope);
|
||||
let mut context_state = context_state_rc.borrow_mut();
|
||||
let promise_value =
|
||||
v8::Local::<v8::Promise>::try_from(promise.v8_value).unwrap();
|
||||
let promise_global = v8::Global::new(scope, promise_value);
|
||||
context_state
|
||||
.pending_promise_rejections
|
||||
.retain(|(key, _)| key != &promise_global);
|
||||
}
|
||||
|
||||
#[op(v8)]
|
||||
fn op_has_pending_promise_rejection<'a>(
|
||||
scope: &mut v8::HandleScope<'a>,
|
||||
promise: serde_v8::Value<'a>,
|
||||
) -> bool {
|
||||
let context_state_rc = JsRealm::state_from_scope(scope);
|
||||
let context_state = context_state_rc.borrow();
|
||||
let promise_value =
|
||||
v8::Local::<v8::Promise>::try_from(promise.v8_value).unwrap();
|
||||
let promise_global = v8::Global::new(scope, promise_value);
|
||||
context_state
|
||||
.pending_promise_rejections
|
||||
.iter()
|
||||
.any(|(key, _)| key == &promise_global)
|
||||
}
|
||||
|
||||
#[op(v8)]
|
||||
fn op_arraybuffer_was_detached<'a>(
|
||||
_scope: &mut v8::HandleScope<'a>,
|
||||
input: serde_v8::Value<'a>,
|
||||
) -> Result<bool, Error> {
|
||||
let ab = v8::Local::<v8::ArrayBuffer>::try_from(input.v8_value)?;
|
||||
Ok(ab.was_detached())
|
||||
}
|
|
@ -1,91 +0,0 @@
|
|||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
||||
|
||||
use crate::serde::Serialize;
|
||||
use crate::OpId;
|
||||
use std::cell::RefCell;
|
||||
use std::cell::RefMut;
|
||||
|
||||
// TODO(@AaronO): split into AggregateMetrics & PerOpMetrics
|
||||
#[derive(Clone, Default, Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct OpMetrics {
|
||||
pub ops_dispatched: u64,
|
||||
pub ops_dispatched_sync: u64,
|
||||
pub ops_dispatched_async: u64,
|
||||
// TODO(bartlomieju): this field is never updated
|
||||
pub ops_dispatched_async_unref: u64,
|
||||
pub ops_completed: u64,
|
||||
pub ops_completed_sync: u64,
|
||||
pub ops_completed_async: u64,
|
||||
// TODO(bartlomieju): this field is never updated
|
||||
pub ops_completed_async_unref: u64,
|
||||
pub bytes_sent_control: u64,
|
||||
pub bytes_sent_data: u64,
|
||||
pub bytes_received: u64,
|
||||
}
|
||||
|
||||
// TODO(@AaronO): track errors
|
||||
#[derive(Default, Debug)]
|
||||
pub struct OpsTracker {
|
||||
ops: RefCell<Vec<OpMetrics>>,
|
||||
}
|
||||
|
||||
impl OpsTracker {
|
||||
pub fn new(ops_count: usize) -> Self {
|
||||
Self {
|
||||
ops: RefCell::new(vec![Default::default(); ops_count]),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn per_op(&self) -> Vec<OpMetrics> {
|
||||
self.ops.borrow().clone()
|
||||
}
|
||||
|
||||
pub fn aggregate(&self) -> OpMetrics {
|
||||
let mut sum = OpMetrics::default();
|
||||
|
||||
for metrics in self.ops.borrow().iter() {
|
||||
sum.ops_dispatched += metrics.ops_dispatched;
|
||||
sum.ops_dispatched_sync += metrics.ops_dispatched_sync;
|
||||
sum.ops_dispatched_async += metrics.ops_dispatched_async;
|
||||
sum.ops_dispatched_async_unref += metrics.ops_dispatched_async_unref;
|
||||
sum.ops_completed += metrics.ops_completed;
|
||||
sum.ops_completed_sync += metrics.ops_completed_sync;
|
||||
sum.ops_completed_async += metrics.ops_completed_async;
|
||||
sum.ops_completed_async_unref += metrics.ops_completed_async_unref;
|
||||
sum.bytes_sent_control += metrics.bytes_sent_control;
|
||||
sum.bytes_sent_data += metrics.bytes_sent_data;
|
||||
sum.bytes_received += metrics.bytes_received;
|
||||
}
|
||||
|
||||
sum
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn metrics_mut(&self, id: OpId) -> RefMut<OpMetrics> {
|
||||
RefMut::map(self.ops.borrow_mut(), |ops| &mut ops[id as usize])
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn track_sync(&self, id: OpId) {
|
||||
let mut metrics = self.metrics_mut(id);
|
||||
metrics.ops_dispatched += 1;
|
||||
metrics.ops_completed += 1;
|
||||
metrics.ops_dispatched_sync += 1;
|
||||
metrics.ops_completed_sync += 1;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn track_async(&self, id: OpId) {
|
||||
let mut metrics = self.metrics_mut(id);
|
||||
metrics.ops_dispatched += 1;
|
||||
metrics.ops_dispatched_async += 1;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn track_async_completed(&self, id: OpId) {
|
||||
let mut metrics = self.metrics_mut(id);
|
||||
metrics.ops_completed += 1;
|
||||
metrics.ops_completed_async += 1;
|
||||
}
|
||||
}
|
91
core/path.rs
91
core/path.rs
|
@ -1,91 +0,0 @@
|
|||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[cfg(not(windows))]
|
||||
#[inline]
|
||||
pub fn strip_unc_prefix(path: PathBuf) -> PathBuf {
|
||||
path
|
||||
}
|
||||
|
||||
/// Strips the unc prefix (ex. \\?\) from Windows paths.
|
||||
#[cfg(windows)]
|
||||
pub fn strip_unc_prefix(path: PathBuf) -> PathBuf {
|
||||
use std::path::Component;
|
||||
use std::path::Prefix;
|
||||
|
||||
let mut components = path.components();
|
||||
match components.next() {
|
||||
Some(Component::Prefix(prefix)) => {
|
||||
match prefix.kind() {
|
||||
// \\?\device
|
||||
Prefix::Verbatim(device) => {
|
||||
let mut path = PathBuf::new();
|
||||
path.push(format!(r"\\{}\", device.to_string_lossy()));
|
||||
path.extend(components.filter(|c| !matches!(c, Component::RootDir)));
|
||||
path
|
||||
}
|
||||
// \\?\c:\path
|
||||
Prefix::VerbatimDisk(_) => {
|
||||
let mut path = PathBuf::new();
|
||||
path.push(prefix.as_os_str().to_string_lossy().replace(r"\\?\", ""));
|
||||
path.extend(components);
|
||||
path
|
||||
}
|
||||
// \\?\UNC\hostname\share_name\path
|
||||
Prefix::VerbatimUNC(hostname, share_name) => {
|
||||
let mut path = PathBuf::new();
|
||||
path.push(format!(
|
||||
r"\\{}\{}\",
|
||||
hostname.to_string_lossy(),
|
||||
share_name.to_string_lossy()
|
||||
));
|
||||
path.extend(components.filter(|c| !matches!(c, Component::RootDir)));
|
||||
path
|
||||
}
|
||||
_ => path,
|
||||
}
|
||||
}
|
||||
_ => path,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
#[cfg(windows)]
|
||||
#[test]
|
||||
fn test_strip_unc_prefix() {
|
||||
use std::path::PathBuf;
|
||||
|
||||
run_test(r"C:\", r"C:\");
|
||||
run_test(r"C:\test\file.txt", r"C:\test\file.txt");
|
||||
|
||||
run_test(r"\\?\C:\", r"C:\");
|
||||
run_test(r"\\?\C:\test\file.txt", r"C:\test\file.txt");
|
||||
|
||||
run_test(r"\\.\C:\", r"\\.\C:\");
|
||||
run_test(r"\\.\C:\Test\file.txt", r"\\.\C:\Test\file.txt");
|
||||
|
||||
run_test(r"\\?\UNC\localhost\", r"\\localhost");
|
||||
run_test(r"\\?\UNC\localhost\c$\", r"\\localhost\c$");
|
||||
run_test(
|
||||
r"\\?\UNC\localhost\c$\Windows\file.txt",
|
||||
r"\\localhost\c$\Windows\file.txt",
|
||||
);
|
||||
run_test(r"\\?\UNC\wsl$\deno.json", r"\\wsl$\deno.json");
|
||||
|
||||
run_test(r"\\?\server1", r"\\server1");
|
||||
run_test(r"\\?\server1\e$\", r"\\server1\e$\");
|
||||
run_test(
|
||||
r"\\?\server1\e$\test\file.txt",
|
||||
r"\\server1\e$\test\file.txt",
|
||||
);
|
||||
|
||||
fn run_test(input: &str, expected: &str) {
|
||||
assert_eq!(
|
||||
super::strip_unc_prefix(PathBuf::from(input)),
|
||||
PathBuf::from(expected)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,429 +0,0 @@
|
|||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
||||
|
||||
// Think of Resources as File Descriptors. They are integers that are allocated
|
||||
// by the privileged side of Deno which refer to various rust objects that need
|
||||
// to be persisted between various ops. For example, network sockets are
|
||||
// resources. Resources may or may not correspond to a real operating system
|
||||
// file descriptor (hence the different name).
|
||||
|
||||
use crate::error::bad_resource_id;
|
||||
use crate::error::not_supported;
|
||||
use crate::io::BufMutView;
|
||||
use crate::io::BufView;
|
||||
use crate::io::WriteOutcome;
|
||||
use anyhow::Error;
|
||||
use futures::Future;
|
||||
use std::any::type_name;
|
||||
use std::any::Any;
|
||||
use std::any::TypeId;
|
||||
use std::borrow::Cow;
|
||||
use std::collections::BTreeMap;
|
||||
use std::iter::Iterator;
|
||||
use std::pin::Pin;
|
||||
use std::rc::Rc;
|
||||
|
||||
/// Returned by resource read/write/shutdown methods
|
||||
pub type AsyncResult<T> = Pin<Box<dyn Future<Output = Result<T, Error>>>>;
|
||||
|
||||
/// Resources are Rust objects that are attached to a [deno_core::JsRuntime].
|
||||
/// They are identified in JS by a numeric ID (the resource ID, or rid).
|
||||
/// Resources can be created in ops. Resources can also be retrieved in ops by
|
||||
/// their rid. Resources are not thread-safe - they can only be accessed from
|
||||
/// the thread that the JsRuntime lives on.
|
||||
///
|
||||
/// Resources are reference counted in Rust. This means that they can be
|
||||
/// cloned and passed around. When the last reference is dropped, the resource
|
||||
/// is automatically closed. As long as the resource exists in the resource
|
||||
/// table, the reference count is at least 1.
|
||||
///
|
||||
/// ### Readable
|
||||
///
|
||||
/// Readable resources are resources that can have data read from. Examples of
|
||||
/// this are files, sockets, or HTTP streams.
|
||||
///
|
||||
/// Readables can be read from from either JS or Rust. In JS one can use
|
||||
/// `Deno.core.read()` to read from a single chunk of data from a readable. In
|
||||
/// Rust one can directly call `read()` or `read_byob()`. The Rust side code is
|
||||
/// used to implement ops like `op_slice`.
|
||||
///
|
||||
/// A distinction can be made between readables that produce chunks of data
|
||||
/// themselves (they allocate the chunks), and readables that fill up
|
||||
/// bring-your-own-buffers (BYOBs). The former is often the case for framed
|
||||
/// protocols like HTTP, while the latter is often the case for kernel backed
|
||||
/// resources like files and sockets.
|
||||
///
|
||||
/// All readables must implement `read()`. If resources can support an optimized
|
||||
/// path for BYOBs, they should also implement `read_byob()`. For kernel backed
|
||||
/// resources it often makes sense to implement `read_byob()` first, and then
|
||||
/// implement `read()` as an operation that allocates a new chunk with
|
||||
/// `len == limit`, then calls `read_byob()`, and then returns a chunk sliced to
|
||||
/// the number of bytes read. Kernel backed resources can use the
|
||||
/// [deno_core::impl_readable_byob] macro to implement optimized `read_byob()`
|
||||
/// and `read()` implementations from a single `Self::read()` method.
|
||||
///
|
||||
/// ### Writable
|
||||
///
|
||||
/// Writable resources are resources that can have data written to. Examples of
|
||||
/// this are files, sockets, or HTTP streams.
|
||||
///
|
||||
/// Writables can be written to from either JS or Rust. In JS one can use
|
||||
/// `Deno.core.write()` to write to a single chunk of data to a writable. In
|
||||
/// Rust one can directly call `write()`. The latter is used to implement ops
|
||||
/// like `op_slice`.
|
||||
pub trait Resource: Any + 'static {
|
||||
/// Returns a string representation of the resource which is made available
|
||||
/// to JavaScript code through `op_resources`. The default implementation
|
||||
/// returns the Rust type name, but specific resource types may override this
|
||||
/// trait method.
|
||||
fn name(&self) -> Cow<str> {
|
||||
type_name::<Self>().into()
|
||||
}
|
||||
|
||||
/// Read a single chunk of data from the resource. This operation returns a
|
||||
/// `BufView` that represents the data that was read. If a zero length buffer
|
||||
/// is returned, it indicates that the resource has reached EOF.
|
||||
///
|
||||
/// If this method is not implemented, the default implementation will error
|
||||
/// with a "not supported" error.
|
||||
///
|
||||
/// If a readable can provide an optimized path for BYOBs, it should also
|
||||
/// implement `read_byob()`.
|
||||
fn read(self: Rc<Self>, limit: usize) -> AsyncResult<BufView> {
|
||||
_ = limit;
|
||||
Box::pin(futures::future::err(not_supported()))
|
||||
}
|
||||
|
||||
/// Read a single chunk of data from the resource into the provided `BufMutView`.
|
||||
///
|
||||
/// This operation returns the number of bytes read. If zero bytes are read,
|
||||
/// it indicates that the resource has reached EOF.
|
||||
///
|
||||
/// If this method is not implemented explicitly, the default implementation
|
||||
/// will call `read()` and then copy the data into the provided buffer. For
|
||||
/// readable resources that can provide an optimized path for BYOBs, it is
|
||||
/// strongly recommended to override this method.
|
||||
fn read_byob(
|
||||
self: Rc<Self>,
|
||||
mut buf: BufMutView,
|
||||
) -> AsyncResult<(usize, BufMutView)> {
|
||||
Box::pin(async move {
|
||||
let read = self.read(buf.len()).await?;
|
||||
let nread = read.len();
|
||||
buf[..nread].copy_from_slice(&read);
|
||||
Ok((nread, buf))
|
||||
})
|
||||
}
|
||||
|
||||
/// Write a single chunk of data to the resource. The operation may not be
|
||||
/// able to write the entire chunk, in which case it should return the number
|
||||
/// of bytes written. Additionally it should return the `BufView` that was
|
||||
/// passed in.
|
||||
///
|
||||
/// If this method is not implemented, the default implementation will error
|
||||
/// with a "not supported" error.
|
||||
fn write(self: Rc<Self>, buf: BufView) -> AsyncResult<WriteOutcome> {
|
||||
_ = buf;
|
||||
Box::pin(futures::future::err(not_supported()))
|
||||
}
|
||||
|
||||
/// Write an entire chunk of data to the resource. Unlike `write()`, this will
|
||||
/// ensure the entire chunk is written. If the operation is not able to write
|
||||
/// the entire chunk, an error is to be returned.
|
||||
///
|
||||
/// By default this method will call `write()` repeatedly until the entire
|
||||
/// chunk is written. Resources that can write the entire chunk in a single
|
||||
/// operation using an optimized path should override this method.
|
||||
fn write_all(self: Rc<Self>, view: BufView) -> AsyncResult<()> {
|
||||
Box::pin(async move {
|
||||
let mut view = view;
|
||||
let this = self;
|
||||
while !view.is_empty() {
|
||||
let resp = this.clone().write(view).await?;
|
||||
match resp {
|
||||
WriteOutcome::Partial {
|
||||
nwritten,
|
||||
view: new_view,
|
||||
} => {
|
||||
view = new_view;
|
||||
view.advance_cursor(nwritten);
|
||||
}
|
||||
WriteOutcome::Full { .. } => break,
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
/// The same as [`read_byob()`][Resource::read_byob], but synchronous.
|
||||
fn read_byob_sync(self: Rc<Self>, data: &mut [u8]) -> Result<usize, Error> {
|
||||
_ = data;
|
||||
Err(not_supported())
|
||||
}
|
||||
|
||||
/// The same as [`write()`][Resource::write], but synchronous.
|
||||
fn write_sync(self: Rc<Self>, data: &[u8]) -> Result<usize, Error> {
|
||||
_ = data;
|
||||
Err(not_supported())
|
||||
}
|
||||
|
||||
/// The shutdown method can be used to asynchronously close the resource. It
|
||||
/// is not automatically called when the resource is dropped or closed.
|
||||
///
|
||||
/// If this method is not implemented, the default implementation will error
|
||||
/// with a "not supported" error.
|
||||
fn shutdown(self: Rc<Self>) -> AsyncResult<()> {
|
||||
Box::pin(futures::future::err(not_supported()))
|
||||
}
|
||||
|
||||
/// Resources may implement the `close()` trait method if they need to do
|
||||
/// resource specific clean-ups, such as cancelling pending futures, after a
|
||||
/// resource has been removed from the resource table.
|
||||
fn close(self: Rc<Self>) {}
|
||||
|
||||
/// Resources backed by a file descriptor can let ops know to allow for
|
||||
/// low-level optimizations.
|
||||
#[cfg(unix)]
|
||||
fn backing_fd(self: Rc<Self>) -> Option<std::os::unix::prelude::RawFd> {
|
||||
None
|
||||
}
|
||||
|
||||
/// Resources backed by a file descriptor can let ops know to allow for
|
||||
/// low-level optimizations.
|
||||
#[cfg(windows)]
|
||||
fn backing_fd(self: Rc<Self>) -> Option<std::os::windows::io::RawHandle> {
|
||||
None
|
||||
}
|
||||
|
||||
fn size_hint(&self) -> (u64, Option<u64>) {
|
||||
(0, None)
|
||||
}
|
||||
}
|
||||
|
||||
impl dyn Resource {
|
||||
#[inline(always)]
|
||||
fn is<T: Resource>(&self) -> bool {
|
||||
self.type_id() == TypeId::of::<T>()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
#[allow(clippy::needless_lifetimes)]
|
||||
pub fn downcast_rc<'a, T: Resource>(self: &'a Rc<Self>) -> Option<&'a Rc<T>> {
|
||||
if self.is::<T>() {
|
||||
let ptr = self as *const Rc<_> as *const Rc<T>;
|
||||
// TODO(piscisaureus): safety comment
|
||||
#[allow(clippy::undocumented_unsafe_blocks)]
|
||||
Some(unsafe { &*ptr })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A `ResourceId` is an integer value referencing a resource. It could be
|
||||
/// considered to be the Deno equivalent of a `file descriptor` in POSIX like
|
||||
/// operating systems. Elsewhere in the code base it is commonly abbreviated
|
||||
/// to `rid`.
|
||||
// TODO: use `u64` instead?
|
||||
pub type ResourceId = u32;
|
||||
|
||||
/// Map-like data structure storing Deno's resources (equivalent to file
|
||||
/// descriptors).
|
||||
///
|
||||
/// Provides basic methods for element access. A resource can be of any type.
|
||||
/// Different types of resources can be stored in the same map, and provided
|
||||
/// with a name for description.
|
||||
///
|
||||
/// Each resource is identified through a _resource ID (rid)_, which acts as
|
||||
/// the key in the map.
|
||||
#[derive(Default)]
|
||||
pub struct ResourceTable {
|
||||
index: BTreeMap<ResourceId, Rc<dyn Resource>>,
|
||||
next_rid: ResourceId,
|
||||
}
|
||||
|
||||
impl ResourceTable {
|
||||
/// Inserts resource into the resource table, which takes ownership of it.
|
||||
///
|
||||
/// The resource type is erased at runtime and must be statically known
|
||||
/// when retrieving it through `get()`.
|
||||
///
|
||||
/// Returns a unique resource ID, which acts as a key for this resource.
|
||||
pub fn add<T: Resource>(&mut self, resource: T) -> ResourceId {
|
||||
self.add_rc(Rc::new(resource))
|
||||
}
|
||||
|
||||
/// Inserts a `Rc`-wrapped resource into the resource table.
|
||||
///
|
||||
/// The resource type is erased at runtime and must be statically known
|
||||
/// when retrieving it through `get()`.
|
||||
///
|
||||
/// Returns a unique resource ID, which acts as a key for this resource.
|
||||
pub fn add_rc<T: Resource>(&mut self, resource: Rc<T>) -> ResourceId {
|
||||
let resource = resource as Rc<dyn Resource>;
|
||||
self.add_rc_dyn(resource)
|
||||
}
|
||||
|
||||
pub fn add_rc_dyn(&mut self, resource: Rc<dyn Resource>) -> ResourceId {
|
||||
let rid = self.next_rid;
|
||||
let removed_resource = self.index.insert(rid, resource);
|
||||
assert!(removed_resource.is_none());
|
||||
self.next_rid += 1;
|
||||
rid
|
||||
}
|
||||
|
||||
/// Returns true if any resource with the given `rid` exists.
|
||||
pub fn has(&self, rid: ResourceId) -> bool {
|
||||
self.index.contains_key(&rid)
|
||||
}
|
||||
|
||||
/// Returns a reference counted pointer to the resource of type `T` with the
|
||||
/// given `rid`. If `rid` is not present or has a type different than `T`,
|
||||
/// this function returns `None`.
|
||||
pub fn get<T: Resource>(&self, rid: ResourceId) -> Result<Rc<T>, Error> {
|
||||
self
|
||||
.index
|
||||
.get(&rid)
|
||||
.and_then(|rc| rc.downcast_rc::<T>())
|
||||
.map(Clone::clone)
|
||||
.ok_or_else(bad_resource_id)
|
||||
}
|
||||
|
||||
pub fn get_any(&self, rid: ResourceId) -> Result<Rc<dyn Resource>, Error> {
|
||||
self
|
||||
.index
|
||||
.get(&rid)
|
||||
.map(Clone::clone)
|
||||
.ok_or_else(bad_resource_id)
|
||||
}
|
||||
|
||||
/// Replaces a resource with a new resource.
|
||||
///
|
||||
/// Panics if the resource does not exist.
|
||||
pub fn replace<T: Resource>(&mut self, rid: ResourceId, resource: T) {
|
||||
let result = self
|
||||
.index
|
||||
.insert(rid, Rc::new(resource) as Rc<dyn Resource>);
|
||||
assert!(result.is_some());
|
||||
}
|
||||
|
||||
/// Removes a resource of type `T` from the resource table and returns it.
|
||||
/// If a resource with the given `rid` exists but its type does not match `T`,
|
||||
/// it is not removed from the resource table. Note that the resource's
|
||||
/// `close()` method is *not* called.
|
||||
///
|
||||
/// Also note that there might be a case where
|
||||
/// the returned `Rc<T>` is referenced by other variables. That is, we cannot
|
||||
/// assume that `Rc::strong_count(&returned_rc)` is always equal to 1 on success.
|
||||
/// In particular, be really careful when you want to extract the inner value of
|
||||
/// type `T` from `Rc<T>`.
|
||||
pub fn take<T: Resource>(&mut self, rid: ResourceId) -> Result<Rc<T>, Error> {
|
||||
let resource = self.get::<T>(rid)?;
|
||||
self.index.remove(&rid);
|
||||
Ok(resource)
|
||||
}
|
||||
|
||||
/// Removes a resource from the resource table and returns it. Note that the
|
||||
/// resource's `close()` method is *not* called.
|
||||
///
|
||||
/// Also note that there might be a
|
||||
/// case where the returned `Rc<T>` is referenced by other variables. That is,
|
||||
/// we cannot assume that `Rc::strong_count(&returned_rc)` is always equal to 1
|
||||
/// on success. In particular, be really careful when you want to extract the
|
||||
/// inner value of type `T` from `Rc<T>`.
|
||||
pub fn take_any(
|
||||
&mut self,
|
||||
rid: ResourceId,
|
||||
) -> Result<Rc<dyn Resource>, Error> {
|
||||
self.index.remove(&rid).ok_or_else(bad_resource_id)
|
||||
}
|
||||
|
||||
/// Removes the resource with the given `rid` from the resource table. If the
|
||||
/// only reference to this resource existed in the resource table, this will
|
||||
/// cause the resource to be dropped. However, since resources are reference
|
||||
/// counted, therefore pending ops are not automatically cancelled. A resource
|
||||
/// may implement the `close()` method to perform clean-ups such as canceling
|
||||
/// ops.
|
||||
pub fn close(&mut self, rid: ResourceId) -> Result<(), Error> {
|
||||
self
|
||||
.index
|
||||
.remove(&rid)
|
||||
.ok_or_else(bad_resource_id)
|
||||
.map(|resource| resource.close())
|
||||
}
|
||||
|
||||
/// Returns an iterator that yields a `(id, name)` pair for every resource
|
||||
/// that's currently in the resource table. This can be used for debugging
|
||||
/// purposes or to implement the `op_resources` op. Note that the order in
|
||||
/// which items appear is not specified.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use deno_core::ResourceTable;
|
||||
/// # let resource_table = ResourceTable::default();
|
||||
/// let resource_names = resource_table.names().collect::<Vec<_>>();
|
||||
/// ```
|
||||
pub fn names(&self) -> impl Iterator<Item = (ResourceId, Cow<str>)> {
|
||||
self
|
||||
.index
|
||||
.iter()
|
||||
.map(|(&id, resource)| (id, resource.name()))
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! impl_readable_byob {
|
||||
() => {
|
||||
fn read(self: Rc<Self>, limit: usize) -> AsyncResult<$crate::BufView> {
|
||||
Box::pin(async move {
|
||||
let mut vec = vec![0; limit];
|
||||
let nread = self.read(&mut vec).await?;
|
||||
if nread != vec.len() {
|
||||
vec.truncate(nread);
|
||||
}
|
||||
let view = $crate::BufView::from(vec);
|
||||
Ok(view)
|
||||
})
|
||||
}
|
||||
|
||||
fn read_byob(
|
||||
self: Rc<Self>,
|
||||
mut buf: $crate::BufMutView,
|
||||
) -> AsyncResult<(usize, $crate::BufMutView)> {
|
||||
Box::pin(async move {
|
||||
let nread = self.read(buf.as_mut()).await?;
|
||||
Ok((nread, buf))
|
||||
})
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! impl_writable {
|
||||
(__write) => {
|
||||
fn write(
|
||||
self: Rc<Self>,
|
||||
view: $crate::BufView,
|
||||
) -> AsyncResult<$crate::WriteOutcome> {
|
||||
Box::pin(async move {
|
||||
let nwritten = self.write(&view).await?;
|
||||
Ok($crate::WriteOutcome::Partial { nwritten, view })
|
||||
})
|
||||
}
|
||||
};
|
||||
(__write_all) => {
|
||||
fn write_all(self: Rc<Self>, view: $crate::BufView) -> AsyncResult<()> {
|
||||
Box::pin(async move {
|
||||
self.write_all(&view).await?;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
};
|
||||
() => {
|
||||
$crate::impl_writable!(__write);
|
||||
};
|
||||
(with_all) => {
|
||||
$crate::impl_writable!(__write);
|
||||
$crate::impl_writable!(__write_all);
|
||||
};
|
||||
}
|
|
@ -1,51 +0,0 @@
|
|||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
||||
if (!globalThis.Deno) {
|
||||
globalThis.Deno = {
|
||||
core: {
|
||||
ops: {},
|
||||
asyncOps: {},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
Deno.__op__console = function (callConsole, console) {
|
||||
Deno.core.callConsole = callConsole;
|
||||
Deno.core.console = console;
|
||||
};
|
||||
|
||||
Deno.__op__registerOp = function (isAsync, op, opName) {
|
||||
const core = Deno.core;
|
||||
if (isAsync) {
|
||||
if (core.ops[opName] !== undefined) {
|
||||
return;
|
||||
}
|
||||
core.asyncOps[opName] = op;
|
||||
const fn = function (...args) {
|
||||
if (this !== core.ops) {
|
||||
// deno-lint-ignore prefer-primordials
|
||||
throw new Error(
|
||||
"An async stub cannot be separated from Deno.core.ops. Use ???",
|
||||
);
|
||||
}
|
||||
return core.asyncStub(opName, args);
|
||||
};
|
||||
fn.name = opName;
|
||||
core.ops[opName] = fn;
|
||||
} else {
|
||||
core.ops[opName] = op;
|
||||
}
|
||||
};
|
||||
|
||||
Deno.__op__unregisterOp = function (isAsync, opName) {
|
||||
if (isAsync) {
|
||||
delete Deno.core.asyncOps[opName];
|
||||
}
|
||||
delete Deno.core.ops[opName];
|
||||
};
|
||||
|
||||
Deno.__op__cleanup = function () {
|
||||
delete Deno.__op__console;
|
||||
delete Deno.__op__registerOp;
|
||||
delete Deno.__op__unregisterOp;
|
||||
delete Deno.__op__cleanup;
|
||||
};
|
|
@ -1,545 +0,0 @@
|
|||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
||||
|
||||
use log::debug;
|
||||
use std::fmt::Write;
|
||||
use std::option::Option;
|
||||
use std::os::raw::c_void;
|
||||
use v8::MapFnTo;
|
||||
|
||||
use crate::error::is_instance_of_error;
|
||||
use crate::error::throw_type_error;
|
||||
use crate::error::JsStackFrame;
|
||||
use crate::modules::get_asserted_module_type_from_assertions;
|
||||
use crate::modules::parse_import_assertions;
|
||||
use crate::modules::validate_import_assertions;
|
||||
use crate::modules::ImportAssertionsKind;
|
||||
use crate::modules::ModuleMap;
|
||||
use crate::modules::ResolutionKind;
|
||||
use crate::ops::OpCtx;
|
||||
use crate::runtime::InitMode;
|
||||
use crate::JsRealm;
|
||||
use crate::JsRuntime;
|
||||
|
||||
pub(crate) fn external_references(ops: &[OpCtx]) -> v8::ExternalReferences {
|
||||
// Overallocate a bit, it's better than having to resize the vector.
|
||||
let mut references = Vec::with_capacity(4 + ops.len() * 4);
|
||||
|
||||
references.push(v8::ExternalReference {
|
||||
function: call_console.map_fn_to(),
|
||||
});
|
||||
references.push(v8::ExternalReference {
|
||||
function: import_meta_resolve.map_fn_to(),
|
||||
});
|
||||
references.push(v8::ExternalReference {
|
||||
function: catch_dynamic_import_promise_error.map_fn_to(),
|
||||
});
|
||||
references.push(v8::ExternalReference {
|
||||
function: empty_fn.map_fn_to(),
|
||||
});
|
||||
|
||||
for ctx in ops {
|
||||
let ctx_ptr = ctx as *const OpCtx as _;
|
||||
references.push(v8::ExternalReference { pointer: ctx_ptr });
|
||||
references.push(v8::ExternalReference {
|
||||
function: ctx.decl.v8_fn_ptr,
|
||||
});
|
||||
if let Some(fast_fn) = &ctx.decl.fast_fn {
|
||||
references.push(v8::ExternalReference {
|
||||
pointer: fast_fn.function as _,
|
||||
});
|
||||
references.push(v8::ExternalReference {
|
||||
pointer: ctx.fast_fn_c_info.unwrap().as_ptr() as _,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let refs = v8::ExternalReferences::new(&references);
|
||||
// Leak, V8 takes ownership of the references.
|
||||
std::mem::forget(references);
|
||||
refs
|
||||
}
|
||||
|
||||
// TODO(nayeemrmn): Move to runtime and/or make `pub(crate)`.
|
||||
pub fn script_origin<'a>(
|
||||
s: &mut v8::HandleScope<'a>,
|
||||
resource_name: v8::Local<'a, v8::String>,
|
||||
) -> v8::ScriptOrigin<'a> {
|
||||
let source_map_url = v8::String::empty(s);
|
||||
v8::ScriptOrigin::new(
|
||||
s,
|
||||
resource_name.into(),
|
||||
0,
|
||||
0,
|
||||
false,
|
||||
123,
|
||||
source_map_url.into(),
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
fn get<'s, T>(
|
||||
scope: &mut v8::HandleScope<'s>,
|
||||
from: v8::Local<v8::Object>,
|
||||
key: &'static [u8],
|
||||
path: &'static str,
|
||||
) -> T
|
||||
where
|
||||
v8::Local<'s, v8::Value>: TryInto<T>,
|
||||
{
|
||||
let key = v8::String::new_external_onebyte_static(scope, key).unwrap();
|
||||
from
|
||||
.get(scope, key.into())
|
||||
.unwrap_or_else(|| panic!("{path} exists"))
|
||||
.try_into()
|
||||
.unwrap_or_else(|_| panic!("unable to convert"))
|
||||
}
|
||||
|
||||
pub(crate) fn initialize_context<'s>(
|
||||
scope: &mut v8::HandleScope<'s>,
|
||||
context: v8::Local<'s, v8::Context>,
|
||||
op_ctxs: &[OpCtx],
|
||||
init_mode: InitMode,
|
||||
) -> v8::Local<'s, v8::Context> {
|
||||
let global = context.global(scope);
|
||||
|
||||
let mut codegen = String::with_capacity(op_ctxs.len() * 200);
|
||||
codegen.push_str(include_str!("bindings.js"));
|
||||
_ = writeln!(
|
||||
codegen,
|
||||
"Deno.__op__ = function(opFns, callConsole, console) {{"
|
||||
);
|
||||
if init_mode == InitMode::New {
|
||||
_ = writeln!(codegen, "Deno.__op__console(callConsole, console);");
|
||||
}
|
||||
for op_ctx in op_ctxs {
|
||||
if op_ctx.decl.enabled {
|
||||
_ = writeln!(
|
||||
codegen,
|
||||
"Deno.__op__registerOp({}, opFns[{}], \"{}\");",
|
||||
op_ctx.decl.is_async, op_ctx.id, op_ctx.decl.name
|
||||
);
|
||||
} else {
|
||||
_ = writeln!(
|
||||
codegen,
|
||||
"Deno.__op__unregisterOp({}, \"{}\");",
|
||||
op_ctx.decl.is_async, op_ctx.decl.name
|
||||
);
|
||||
}
|
||||
}
|
||||
codegen.push_str("Deno.__op__cleanup();");
|
||||
_ = writeln!(codegen, "}}");
|
||||
|
||||
let script = v8::String::new_from_one_byte(
|
||||
scope,
|
||||
codegen.as_bytes(),
|
||||
v8::NewStringType::Normal,
|
||||
)
|
||||
.unwrap();
|
||||
let script = v8::Script::compile(scope, script, None).unwrap();
|
||||
script.run(scope);
|
||||
|
||||
let deno = get(scope, global, b"Deno", "Deno");
|
||||
let op_fn: v8::Local<v8::Function> =
|
||||
get(scope, deno, b"__op__", "Deno.__op__");
|
||||
let recv = v8::undefined(scope);
|
||||
let op_fns = v8::Array::new(scope, op_ctxs.len() as i32);
|
||||
for op_ctx in op_ctxs {
|
||||
let op_fn = op_ctx_function(scope, op_ctx);
|
||||
op_fns.set_index(scope, op_ctx.id as u32, op_fn.into());
|
||||
}
|
||||
if init_mode == InitMode::FromSnapshot {
|
||||
op_fn.call(scope, recv.into(), &[op_fns.into()]);
|
||||
} else {
|
||||
// Bind functions to Deno.core.*
|
||||
let call_console_fn = v8::Function::new(scope, call_console).unwrap();
|
||||
|
||||
// Bind v8 console object to Deno.core.console
|
||||
let extra_binding_obj = context.get_extras_binding_object(scope);
|
||||
let console_obj: v8::Local<v8::Object> = get(
|
||||
scope,
|
||||
extra_binding_obj,
|
||||
b"console",
|
||||
"ExtrasBindingObject.console",
|
||||
);
|
||||
|
||||
op_fn.call(
|
||||
scope,
|
||||
recv.into(),
|
||||
&[op_fns.into(), call_console_fn.into(), console_obj.into()],
|
||||
);
|
||||
}
|
||||
|
||||
context
|
||||
}
|
||||
|
||||
fn op_ctx_function<'s>(
|
||||
scope: &mut v8::HandleScope<'s>,
|
||||
op_ctx: &OpCtx,
|
||||
) -> v8::Local<'s, v8::Function> {
|
||||
let op_ctx_ptr = op_ctx as *const OpCtx as *const c_void;
|
||||
let external = v8::External::new(scope, op_ctx_ptr as *mut c_void);
|
||||
let v8name =
|
||||
v8::String::new_external_onebyte_static(scope, op_ctx.decl.name.as_bytes())
|
||||
.unwrap();
|
||||
let builder: v8::FunctionBuilder<v8::FunctionTemplate> =
|
||||
v8::FunctionTemplate::builder_raw(op_ctx.decl.v8_fn_ptr)
|
||||
.data(external.into())
|
||||
.length(op_ctx.decl.arg_count as i32);
|
||||
|
||||
let template = if let Some(fast_function) = &op_ctx.decl.fast_fn {
|
||||
builder.build_fast(
|
||||
scope,
|
||||
fast_function,
|
||||
Some(op_ctx.fast_fn_c_info.unwrap().as_ptr()),
|
||||
None,
|
||||
None,
|
||||
)
|
||||
} else {
|
||||
builder.build(scope)
|
||||
};
|
||||
|
||||
let v8fn = template.get_function(scope).unwrap();
|
||||
v8fn.set_name(v8name);
|
||||
v8fn
|
||||
}
|
||||
|
||||
pub extern "C" fn wasm_async_resolve_promise_callback(
|
||||
_isolate: *mut v8::Isolate,
|
||||
context: v8::Local<v8::Context>,
|
||||
resolver: v8::Local<v8::PromiseResolver>,
|
||||
compilation_result: v8::Local<v8::Value>,
|
||||
success: v8::WasmAsyncSuccess,
|
||||
) {
|
||||
// SAFETY: `CallbackScope` can be safely constructed from `Local<Context>`
|
||||
let scope = &mut unsafe { v8::CallbackScope::new(context) };
|
||||
if success == v8::WasmAsyncSuccess::Success {
|
||||
resolver.resolve(scope, compilation_result).unwrap();
|
||||
} else {
|
||||
resolver.reject(scope, compilation_result).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn host_import_module_dynamically_callback<'s>(
|
||||
scope: &mut v8::HandleScope<'s>,
|
||||
_host_defined_options: v8::Local<'s, v8::Data>,
|
||||
resource_name: v8::Local<'s, v8::Value>,
|
||||
specifier: v8::Local<'s, v8::String>,
|
||||
import_assertions: v8::Local<'s, v8::FixedArray>,
|
||||
) -> Option<v8::Local<'s, v8::Promise>> {
|
||||
// NOTE(bartlomieju): will crash for non-UTF-8 specifier
|
||||
let specifier_str = specifier
|
||||
.to_string(scope)
|
||||
.unwrap()
|
||||
.to_rust_string_lossy(scope);
|
||||
let referrer_name_str = resource_name
|
||||
.to_string(scope)
|
||||
.unwrap()
|
||||
.to_rust_string_lossy(scope);
|
||||
|
||||
let resolver = v8::PromiseResolver::new(scope).unwrap();
|
||||
let promise = resolver.get_promise(scope);
|
||||
|
||||
let assertions = parse_import_assertions(
|
||||
scope,
|
||||
import_assertions,
|
||||
ImportAssertionsKind::DynamicImport,
|
||||
);
|
||||
|
||||
{
|
||||
let tc_scope = &mut v8::TryCatch::new(scope);
|
||||
validate_import_assertions(tc_scope, &assertions);
|
||||
if tc_scope.has_caught() {
|
||||
let e = tc_scope.exception().unwrap();
|
||||
resolver.reject(tc_scope, e);
|
||||
}
|
||||
}
|
||||
let asserted_module_type =
|
||||
get_asserted_module_type_from_assertions(&assertions);
|
||||
|
||||
let resolver_handle = v8::Global::new(scope, resolver);
|
||||
{
|
||||
let state_rc = JsRuntime::state_from(scope);
|
||||
let module_map_rc = JsRuntime::module_map_from(scope);
|
||||
|
||||
debug!(
|
||||
"dyn_import specifier {} referrer {} ",
|
||||
specifier_str, referrer_name_str
|
||||
);
|
||||
ModuleMap::load_dynamic_import(
|
||||
module_map_rc,
|
||||
&specifier_str,
|
||||
&referrer_name_str,
|
||||
asserted_module_type,
|
||||
resolver_handle,
|
||||
);
|
||||
state_rc.borrow_mut().notify_new_dynamic_import();
|
||||
}
|
||||
// Map errors from module resolution (not JS errors from module execution) to
|
||||
// ones rethrown from this scope, so they include the call stack of the
|
||||
// dynamic import site. Error objects without any stack frames are assumed to
|
||||
// be module resolution errors, other exception values are left as they are.
|
||||
let builder = v8::FunctionBuilder::new(catch_dynamic_import_promise_error);
|
||||
|
||||
let map_err =
|
||||
v8::FunctionBuilder::<v8::Function>::build(builder, scope).unwrap();
|
||||
|
||||
let promise = promise.catch(scope, map_err).unwrap();
|
||||
|
||||
Some(promise)
|
||||
}
|
||||
|
||||
pub extern "C" fn host_initialize_import_meta_object_callback(
|
||||
context: v8::Local<v8::Context>,
|
||||
module: v8::Local<v8::Module>,
|
||||
meta: v8::Local<v8::Object>,
|
||||
) {
|
||||
// SAFETY: `CallbackScope` can be safely constructed from `Local<Context>`
|
||||
let scope = &mut unsafe { v8::CallbackScope::new(context) };
|
||||
let module_map_rc = JsRuntime::module_map_from(scope);
|
||||
let module_map = module_map_rc.borrow();
|
||||
|
||||
let module_global = v8::Global::new(scope, module);
|
||||
let info = module_map
|
||||
.get_info(&module_global)
|
||||
.expect("Module not found");
|
||||
|
||||
let url_key = v8::String::new_external_onebyte_static(scope, b"url").unwrap();
|
||||
let url_val = info.name.v8(scope);
|
||||
meta.create_data_property(scope, url_key.into(), url_val.into());
|
||||
|
||||
let main_key =
|
||||
v8::String::new_external_onebyte_static(scope, b"main").unwrap();
|
||||
let main_val = v8::Boolean::new(scope, info.main);
|
||||
meta.create_data_property(scope, main_key.into(), main_val.into());
|
||||
|
||||
let builder =
|
||||
v8::FunctionBuilder::new(import_meta_resolve).data(url_val.into());
|
||||
let val = v8::FunctionBuilder::<v8::Function>::build(builder, scope).unwrap();
|
||||
let resolve_key =
|
||||
v8::String::new_external_onebyte_static(scope, b"resolve").unwrap();
|
||||
meta.set(scope, resolve_key.into(), val.into());
|
||||
}
|
||||
|
||||
fn import_meta_resolve(
|
||||
scope: &mut v8::HandleScope,
|
||||
args: v8::FunctionCallbackArguments,
|
||||
mut rv: v8::ReturnValue,
|
||||
) {
|
||||
if args.length() > 1 {
|
||||
return throw_type_error(scope, "Invalid arguments");
|
||||
}
|
||||
|
||||
let maybe_arg_str = args.get(0).to_string(scope);
|
||||
if maybe_arg_str.is_none() {
|
||||
return throw_type_error(scope, "Invalid arguments");
|
||||
}
|
||||
let specifier = maybe_arg_str.unwrap();
|
||||
let referrer = {
|
||||
let url_prop = args.data();
|
||||
url_prop.to_rust_string_lossy(scope)
|
||||
};
|
||||
let module_map_rc = JsRuntime::module_map_from(scope);
|
||||
let loader = module_map_rc.borrow().loader.clone();
|
||||
let specifier_str = specifier.to_rust_string_lossy(scope);
|
||||
|
||||
if specifier_str.starts_with("npm:") {
|
||||
throw_type_error(scope, "\"npm:\" specifiers are currently not supported in import.meta.resolve()");
|
||||
return;
|
||||
}
|
||||
|
||||
match loader.resolve(&specifier_str, &referrer, ResolutionKind::DynamicImport)
|
||||
{
|
||||
Ok(resolved) => {
|
||||
let resolved_val = serde_v8::to_v8(scope, resolved.as_str()).unwrap();
|
||||
rv.set(resolved_val);
|
||||
}
|
||||
Err(err) => {
|
||||
throw_type_error(scope, &err.to_string());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fn empty_fn(
|
||||
_scope: &mut v8::HandleScope,
|
||||
_args: v8::FunctionCallbackArguments,
|
||||
_rv: v8::ReturnValue,
|
||||
) {
|
||||
//Do Nothing
|
||||
}
|
||||
|
||||
//It creates a reference to an empty function which can be maintained after the snapshots
|
||||
pub fn create_empty_fn<'s>(
|
||||
scope: &mut v8::HandleScope<'s>,
|
||||
) -> Option<v8::Local<'s, v8::Function>> {
|
||||
let empty_fn = v8::FunctionTemplate::new(scope, empty_fn);
|
||||
empty_fn.get_function(scope)
|
||||
}
|
||||
|
||||
fn catch_dynamic_import_promise_error(
|
||||
scope: &mut v8::HandleScope,
|
||||
args: v8::FunctionCallbackArguments,
|
||||
_rv: v8::ReturnValue,
|
||||
) {
|
||||
let arg = args.get(0);
|
||||
if is_instance_of_error(scope, arg) {
|
||||
let e: crate::error::NativeJsError = serde_v8::from_v8(scope, arg).unwrap();
|
||||
let name = e.name.unwrap_or_else(|| "Error".to_string());
|
||||
let msg = v8::Exception::create_message(scope, arg);
|
||||
if msg.get_stack_trace(scope).unwrap().get_frame_count() == 0 {
|
||||
let arg: v8::Local<v8::Object> = arg.try_into().unwrap();
|
||||
let message_key =
|
||||
v8::String::new_external_onebyte_static(scope, b"message").unwrap();
|
||||
let message = arg.get(scope, message_key.into()).unwrap();
|
||||
let mut message: v8::Local<v8::String> = message.try_into().unwrap();
|
||||
if let Some(stack_frame) = JsStackFrame::from_v8_message(scope, msg) {
|
||||
if let Some(location) = stack_frame.maybe_format_location() {
|
||||
let str =
|
||||
format!("{} at {location}", message.to_rust_string_lossy(scope));
|
||||
message = v8::String::new(scope, &str).unwrap();
|
||||
}
|
||||
}
|
||||
let exception = match name.as_str() {
|
||||
"RangeError" => v8::Exception::range_error(scope, message),
|
||||
"TypeError" => v8::Exception::type_error(scope, message),
|
||||
"SyntaxError" => v8::Exception::syntax_error(scope, message),
|
||||
"ReferenceError" => v8::Exception::reference_error(scope, message),
|
||||
_ => v8::Exception::error(scope, message),
|
||||
};
|
||||
let code_key =
|
||||
v8::String::new_external_onebyte_static(scope, b"code").unwrap();
|
||||
let code_value =
|
||||
v8::String::new_external_onebyte_static(scope, b"ERR_MODULE_NOT_FOUND")
|
||||
.unwrap();
|
||||
let exception_obj = exception.to_object(scope).unwrap();
|
||||
exception_obj.set(scope, code_key.into(), code_value.into());
|
||||
scope.throw_exception(exception);
|
||||
return;
|
||||
}
|
||||
}
|
||||
scope.throw_exception(arg);
|
||||
}
|
||||
|
||||
pub extern "C" fn promise_reject_callback(message: v8::PromiseRejectMessage) {
|
||||
use v8::PromiseRejectEvent::*;
|
||||
|
||||
// SAFETY: `CallbackScope` can be safely constructed from `&PromiseRejectMessage`
|
||||
let scope = &mut unsafe { v8::CallbackScope::new(&message) };
|
||||
|
||||
let context_state_rc = JsRealm::state_from_scope(scope);
|
||||
let mut context_state = context_state_rc.borrow_mut();
|
||||
|
||||
if let Some(js_promise_reject_cb) = context_state.js_promise_reject_cb.clone()
|
||||
{
|
||||
drop(context_state);
|
||||
|
||||
let tc_scope = &mut v8::TryCatch::new(scope);
|
||||
let undefined: v8::Local<v8::Value> = v8::undefined(tc_scope).into();
|
||||
let type_ = v8::Integer::new(tc_scope, message.get_event() as i32);
|
||||
let promise = message.get_promise();
|
||||
|
||||
let reason = match message.get_event() {
|
||||
PromiseRejectWithNoHandler
|
||||
| PromiseRejectAfterResolved
|
||||
| PromiseResolveAfterResolved => message.get_value().unwrap_or(undefined),
|
||||
PromiseHandlerAddedAfterReject => undefined,
|
||||
};
|
||||
|
||||
let promise_global = v8::Global::new(tc_scope, promise);
|
||||
let args = &[type_.into(), promise.into(), reason];
|
||||
let maybe_has_unhandled_rejection_handler = js_promise_reject_cb
|
||||
.open(tc_scope)
|
||||
.call(tc_scope, undefined, args);
|
||||
|
||||
let has_unhandled_rejection_handler =
|
||||
if let Some(value) = maybe_has_unhandled_rejection_handler {
|
||||
value.is_true()
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
if has_unhandled_rejection_handler {
|
||||
let state_rc = JsRuntime::state_from(tc_scope);
|
||||
let mut state = state_rc.borrow_mut();
|
||||
if let Some(pending_mod_evaluate) = state.pending_mod_evaluate.as_mut() {
|
||||
if !pending_mod_evaluate.has_evaluated {
|
||||
pending_mod_evaluate
|
||||
.handled_promise_rejections
|
||||
.push(promise_global);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let promise = message.get_promise();
|
||||
let promise_global = v8::Global::new(scope, promise);
|
||||
match message.get_event() {
|
||||
PromiseRejectWithNoHandler => {
|
||||
let error = message.get_value().unwrap();
|
||||
let error_global = v8::Global::new(scope, error);
|
||||
context_state
|
||||
.pending_promise_rejections
|
||||
.push_back((promise_global, error_global));
|
||||
}
|
||||
PromiseHandlerAddedAfterReject => {
|
||||
context_state
|
||||
.pending_promise_rejections
|
||||
.retain(|(key, _)| key != &promise_global);
|
||||
}
|
||||
PromiseRejectAfterResolved => {}
|
||||
PromiseResolveAfterResolved => {
|
||||
// Should not warn. See #1272
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// This binding should be used if there's a custom console implementation
|
||||
/// available. Using it will make sure that proper stack frames are displayed
|
||||
/// in the inspector console.
|
||||
///
|
||||
/// Each method on console object should be bound to this function, eg:
|
||||
/// ```ignore
|
||||
/// function wrapConsole(consoleFromDeno, consoleFromV8) {
|
||||
/// const callConsole = core.callConsole;
|
||||
///
|
||||
/// for (const key of Object.keys(consoleFromV8)) {
|
||||
/// if (consoleFromDeno.hasOwnProperty(key)) {
|
||||
/// consoleFromDeno[key] = callConsole.bind(
|
||||
/// consoleFromDeno,
|
||||
/// consoleFromV8[key],
|
||||
/// consoleFromDeno[key],
|
||||
/// );
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Inspired by:
|
||||
/// https://github.com/nodejs/node/blob/1317252dfe8824fd9cfee125d2aaa94004db2f3b/src/inspector_js_api.cc#L194-L222
|
||||
fn call_console(
|
||||
scope: &mut v8::HandleScope,
|
||||
args: v8::FunctionCallbackArguments,
|
||||
_rv: v8::ReturnValue,
|
||||
) {
|
||||
if args.length() < 2
|
||||
|| !args.get(0).is_function()
|
||||
|| !args.get(1).is_function()
|
||||
{
|
||||
return throw_type_error(scope, "Invalid arguments");
|
||||
}
|
||||
|
||||
let mut call_args = vec![];
|
||||
for i in 2..args.length() {
|
||||
call_args.push(args.get(i));
|
||||
}
|
||||
|
||||
let receiver = args.this();
|
||||
let inspector_console_method =
|
||||
v8::Local::<v8::Function>::try_from(args.get(0)).unwrap();
|
||||
let deno_console_method =
|
||||
v8::Local::<v8::Function>::try_from(args.get(1)).unwrap();
|
||||
|
||||
inspector_console_method.call(scope, receiver.into(), &call_args);
|
||||
deno_console_method.call(scope, receiver.into(), &call_args);
|
||||
}
|
|
@ -1,69 +0,0 @@
|
|||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
||||
"use strict";
|
||||
function assert(cond) {
|
||||
if (!cond) {
|
||||
throw Error("assert");
|
||||
}
|
||||
}
|
||||
|
||||
function assertArrayEquals(a1, a2) {
|
||||
if (a1.length !== a2.length) throw Error("assert");
|
||||
|
||||
for (const index in a1) {
|
||||
if (a1[index] !== a2[index]) {
|
||||
throw Error("assert");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function main() {
|
||||
// deno-fmt-ignore
|
||||
const fixture1 = [
|
||||
0xf0, 0x9d, 0x93, 0xbd,
|
||||
0xf0, 0x9d, 0x93, 0xae,
|
||||
0xf0, 0x9d, 0x94, 0x81,
|
||||
0xf0, 0x9d, 0x93, 0xbd
|
||||
];
|
||||
// deno-fmt-ignore
|
||||
const fixture2 = [
|
||||
72, 101, 108, 108,
|
||||
111, 32, 239, 191,
|
||||
189, 239, 191, 189,
|
||||
32, 87, 111, 114,
|
||||
108, 100
|
||||
];
|
||||
|
||||
const empty = Deno.core.ops.op_encode("");
|
||||
if (empty.length !== 0) throw new Error("assert");
|
||||
|
||||
assertArrayEquals(
|
||||
Array.from(Deno.core.ops.op_encode("𝓽𝓮𝔁𝓽")),
|
||||
fixture1,
|
||||
);
|
||||
assertArrayEquals(
|
||||
Array.from(Deno.core.ops.op_encode("Hello \udc12\ud834 World")),
|
||||
fixture2,
|
||||
);
|
||||
|
||||
const emptyBuf = Deno.core.ops.op_decode(new Uint8Array(0));
|
||||
if (emptyBuf !== "") throw new Error("assert");
|
||||
|
||||
assert(Deno.core.ops.op_decode(new Uint8Array(fixture1)) === "𝓽𝓮𝔁𝓽");
|
||||
assert(
|
||||
Deno.core.ops.op_decode(new Uint8Array(fixture2)) ===
|
||||
"Hello <20><> World",
|
||||
);
|
||||
|
||||
// See https://github.com/denoland/deno/issues/6649
|
||||
let thrown = false;
|
||||
try {
|
||||
Deno.core.ops.op_decode(new Uint8Array(2 ** 29));
|
||||
} catch (e) {
|
||||
thrown = true;
|
||||
assert(e instanceof RangeError);
|
||||
assert(e.message === "string too long");
|
||||
}
|
||||
assert(thrown);
|
||||
}
|
||||
|
||||
main();
|
|
@ -1,32 +0,0 @@
|
|||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
||||
const { core } = Deno;
|
||||
const { ops } = core;
|
||||
|
||||
class DOMException {
|
||||
constructor(message, code) {
|
||||
this.msg = message;
|
||||
this.code = code;
|
||||
}
|
||||
}
|
||||
|
||||
core.registerErrorBuilder(
|
||||
"DOMExceptionOperationError",
|
||||
function DOMExceptionOperationError(msg) {
|
||||
return new DOMException(msg, "OperationError");
|
||||
},
|
||||
);
|
||||
|
||||
try {
|
||||
ops.op_err();
|
||||
throw new Error("op_err didn't throw!");
|
||||
} catch (err) {
|
||||
if (!(err instanceof DOMException)) {
|
||||
throw new Error("err not DOMException");
|
||||
}
|
||||
if (err.msg !== "abc") {
|
||||
throw new Error("err.message is incorrect");
|
||||
}
|
||||
if (err.code !== "OperationError") {
|
||||
throw new Error("err.code is incorrect");
|
||||
}
|
||||
}
|
Binary file not shown.
|
@ -1,365 +0,0 @@
|
|||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
||||
use super::bindings;
|
||||
use crate::error::exception_to_err_result;
|
||||
use crate::joinset::JoinSet;
|
||||
use crate::modules::ModuleCode;
|
||||
use crate::ops::OpCtx;
|
||||
use crate::runtime::JsRuntimeState;
|
||||
use crate::JsRuntime;
|
||||
use crate::OpId;
|
||||
use crate::OpResult;
|
||||
use crate::PromiseId;
|
||||
use anyhow::Error;
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashSet;
|
||||
use std::collections::VecDeque;
|
||||
use std::hash::BuildHasherDefault;
|
||||
use std::hash::Hasher;
|
||||
use std::option::Option;
|
||||
use std::rc::Rc;
|
||||
use v8::HandleScope;
|
||||
use v8::Local;
|
||||
|
||||
// Hasher used for `unrefed_ops`. Since these are rolling i32, there's no
|
||||
// need to actually hash them.
|
||||
#[derive(Default)]
|
||||
pub(crate) struct IdentityHasher(u64);
|
||||
|
||||
impl Hasher for IdentityHasher {
|
||||
fn write_i32(&mut self, i: i32) {
|
||||
self.0 = i as u64;
|
||||
}
|
||||
|
||||
fn finish(&self) -> u64 {
|
||||
self.0
|
||||
}
|
||||
|
||||
fn write(&mut self, _bytes: &[u8]) {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub(crate) struct ContextState {
|
||||
pub(crate) js_event_loop_tick_cb: Option<Rc<v8::Global<v8::Function>>>,
|
||||
pub(crate) js_build_custom_error_cb: Option<Rc<v8::Global<v8::Function>>>,
|
||||
pub(crate) js_promise_reject_cb: Option<Rc<v8::Global<v8::Function>>>,
|
||||
pub(crate) js_format_exception_cb: Option<Rc<v8::Global<v8::Function>>>,
|
||||
pub(crate) js_wasm_streaming_cb: Option<Rc<v8::Global<v8::Function>>>,
|
||||
pub(crate) pending_promise_rejections:
|
||||
VecDeque<(v8::Global<v8::Promise>, v8::Global<v8::Value>)>,
|
||||
pub(crate) unrefed_ops: HashSet<i32, BuildHasherDefault<IdentityHasher>>,
|
||||
pub(crate) pending_ops: JoinSet<(PromiseId, OpId, OpResult)>,
|
||||
// We don't explicitly re-read this prop but need the slice to live alongside
|
||||
// the context
|
||||
pub(crate) op_ctxs: Box<[OpCtx]>,
|
||||
pub(crate) isolate: Option<*mut v8::OwnedIsolate>,
|
||||
}
|
||||
|
||||
/// A representation of a JavaScript realm tied to a [`JsRuntime`], that allows
|
||||
/// execution in the realm's context.
|
||||
///
|
||||
/// A [`JsRealm`] instance is a reference to an already existing realm, which
|
||||
/// does not hold ownership of it, so instances can be created and dropped as
|
||||
/// needed. As such, calling [`JsRealm::new`] doesn't create a new realm, and
|
||||
/// cloning a [`JsRealm`] only creates a new reference. See
|
||||
/// [`JsRuntime::create_realm`] to create new realms instead.
|
||||
///
|
||||
/// Despite [`JsRealm`] instances being references, multiple instances that
|
||||
/// point to the same realm won't overlap because every operation requires
|
||||
/// passing a mutable reference to the [`v8::Isolate`]. Therefore, no operation
|
||||
/// on two [`JsRealm`] instances tied to the same isolate can be run at the same
|
||||
/// time, regardless of whether they point to the same realm.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Every method of [`JsRealm`] will panic if you call it with a reference to a
|
||||
/// [`v8::Isolate`] other than the one that corresponds to the current context.
|
||||
///
|
||||
/// In other words, the [`v8::Isolate`] parameter for all the related [`JsRealm`] methods
|
||||
/// must be extracted from the pre-existing [`JsRuntime`].
|
||||
///
|
||||
/// Example usage with the [`JsRealm::execute_script`] method:
|
||||
/// ```
|
||||
/// use deno_core::JsRuntime;
|
||||
/// use deno_core::RuntimeOptions;
|
||||
///
|
||||
/// let mut runtime = JsRuntime::new(RuntimeOptions::default());
|
||||
/// let new_realm = runtime
|
||||
/// .create_realm()
|
||||
/// .expect("Handle the error properly");
|
||||
/// let source_code = "var a = 0; a + 1";
|
||||
/// let result = new_realm
|
||||
/// .execute_script_static(runtime.v8_isolate(), "<anon>", source_code)
|
||||
/// .expect("Handle the error properly");
|
||||
/// # drop(result);
|
||||
/// ```
|
||||
///
|
||||
/// # Lifetime of the realm
|
||||
///
|
||||
/// As long as the corresponding isolate is alive, a [`JsRealm`] instance will
|
||||
/// keep the underlying V8 context alive even if it would have otherwise been
|
||||
/// garbage collected.
|
||||
#[derive(Clone)]
|
||||
#[repr(transparent)]
|
||||
pub struct JsRealm(pub(crate) JsRealmInner);
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct JsRealmInner {
|
||||
context_state: Rc<RefCell<ContextState>>,
|
||||
context: Rc<v8::Global<v8::Context>>,
|
||||
runtime_state: Rc<RefCell<JsRuntimeState>>,
|
||||
is_global: bool,
|
||||
}
|
||||
|
||||
impl JsRealmInner {
|
||||
pub(crate) fn new(
|
||||
context_state: Rc<RefCell<ContextState>>,
|
||||
context: v8::Global<v8::Context>,
|
||||
runtime_state: Rc<RefCell<JsRuntimeState>>,
|
||||
is_global: bool,
|
||||
) -> Self {
|
||||
Self {
|
||||
context_state,
|
||||
context: context.into(),
|
||||
runtime_state,
|
||||
is_global,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn num_pending_ops(&self) -> usize {
|
||||
self.context_state.borrow().pending_ops.len()
|
||||
}
|
||||
|
||||
pub fn num_unrefed_ops(&self) -> usize {
|
||||
self.context_state.borrow().unrefed_ops.len()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn context(&self) -> &v8::Global<v8::Context> {
|
||||
&self.context
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) fn state(&self) -> Rc<RefCell<ContextState>> {
|
||||
self.context_state.clone()
|
||||
}
|
||||
|
||||
/// For info on the [`v8::Isolate`] parameter, check [`JsRealm#panics`].
|
||||
#[inline(always)]
|
||||
pub fn handle_scope<'s>(
|
||||
&self,
|
||||
isolate: &'s mut v8::Isolate,
|
||||
) -> v8::HandleScope<'s> {
|
||||
v8::HandleScope::with_context(isolate, &*self.context)
|
||||
}
|
||||
|
||||
pub(crate) fn check_promise_rejections(
|
||||
&self,
|
||||
scope: &mut v8::HandleScope,
|
||||
) -> Result<(), Error> {
|
||||
let Some((_, handle)) = self.context_state.borrow_mut().pending_promise_rejections.pop_front() else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
let exception = v8::Local::new(scope, handle);
|
||||
let state_rc = JsRuntime::state_from(scope);
|
||||
let state = state_rc.borrow();
|
||||
if let Some(inspector) = &state.inspector {
|
||||
let inspector = inspector.borrow();
|
||||
inspector.exception_thrown(scope, exception, true);
|
||||
if inspector.has_blocking_sessions() {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
exception_to_err_result(scope, exception, true)
|
||||
}
|
||||
|
||||
pub(crate) fn is_same(&self, other: &Rc<v8::Global<v8::Context>>) -> bool {
|
||||
Rc::ptr_eq(&self.context, other)
|
||||
}
|
||||
|
||||
pub fn destroy(self) {
|
||||
let state = self.state();
|
||||
let raw_ptr = self.state().borrow().isolate.unwrap();
|
||||
// SAFETY: We know the isolate outlives the realm
|
||||
let isolate = unsafe { raw_ptr.as_mut().unwrap() };
|
||||
let mut realm_state = state.borrow_mut();
|
||||
// These globals will prevent snapshots from completing, take them
|
||||
std::mem::take(&mut realm_state.js_event_loop_tick_cb);
|
||||
std::mem::take(&mut realm_state.js_build_custom_error_cb);
|
||||
std::mem::take(&mut realm_state.js_promise_reject_cb);
|
||||
std::mem::take(&mut realm_state.js_format_exception_cb);
|
||||
std::mem::take(&mut realm_state.js_wasm_streaming_cb);
|
||||
// The OpCtx slice may contain a circular reference
|
||||
std::mem::take(&mut realm_state.op_ctxs);
|
||||
|
||||
self.context().open(isolate).clear_all_slots(isolate);
|
||||
|
||||
// Expect that this context is dead (we only check this in debug mode)
|
||||
// TODO(mmastrac): This check fails for some tests, will need to fix this
|
||||
// debug_assert_eq!(Rc::strong_count(&self.context), 1, "Realm was still alive when we wanted to destroy it. Not dropped?");
|
||||
}
|
||||
}
|
||||
|
||||
impl JsRealm {
|
||||
pub(crate) fn new(inner: JsRealmInner) -> Self {
|
||||
Self(inner)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) fn state_from_scope(
|
||||
scope: &mut v8::HandleScope,
|
||||
) -> Rc<RefCell<ContextState>> {
|
||||
let context = scope.get_current_context();
|
||||
context
|
||||
.get_slot::<Rc<RefCell<ContextState>>>(scope)
|
||||
.unwrap()
|
||||
.clone()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn num_pending_ops(&self) -> usize {
|
||||
self.0.num_pending_ops()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn num_unrefed_ops(&self) -> usize {
|
||||
self.0.num_unrefed_ops()
|
||||
}
|
||||
|
||||
/// For info on the [`v8::Isolate`] parameter, check [`JsRealm#panics`].
|
||||
#[inline(always)]
|
||||
pub fn handle_scope<'s>(
|
||||
&self,
|
||||
isolate: &'s mut v8::Isolate,
|
||||
) -> v8::HandleScope<'s> {
|
||||
self.0.handle_scope(isolate)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn context(&self) -> &v8::Global<v8::Context> {
|
||||
self.0.context()
|
||||
}
|
||||
|
||||
/// For info on the [`v8::Isolate`] parameter, check [`JsRealm#panics`].
|
||||
pub fn global_object<'s>(
|
||||
&self,
|
||||
isolate: &'s mut v8::Isolate,
|
||||
) -> v8::Local<'s, v8::Object> {
|
||||
let scope = &mut self.0.handle_scope(isolate);
|
||||
self.0.context.open(scope).global(scope)
|
||||
}
|
||||
|
||||
fn string_from_code<'a>(
|
||||
scope: &mut HandleScope<'a>,
|
||||
code: &ModuleCode,
|
||||
) -> Option<Local<'a, v8::String>> {
|
||||
if let Some(code) = code.try_static_ascii() {
|
||||
v8::String::new_external_onebyte_static(scope, code)
|
||||
} else {
|
||||
v8::String::new_from_utf8(
|
||||
scope,
|
||||
code.as_bytes(),
|
||||
v8::NewStringType::Normal,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Executes traditional JavaScript code (traditional = not ES modules) in the
|
||||
/// realm's context.
|
||||
///
|
||||
/// For info on the [`v8::Isolate`] parameter, check [`JsRealm#panics`].
|
||||
///
|
||||
/// The `name` parameter can be a filepath or any other string. E.g.:
|
||||
///
|
||||
/// - "/some/file/path.js"
|
||||
/// - "<anon>"
|
||||
/// - "[native code]"
|
||||
///
|
||||
/// The same `name` value can be used for multiple executions.
|
||||
///
|
||||
/// `Error` can usually be downcast to `JsError`.
|
||||
pub fn execute_script_static(
|
||||
&self,
|
||||
isolate: &mut v8::Isolate,
|
||||
name: &'static str,
|
||||
source_code: &'static str,
|
||||
) -> Result<v8::Global<v8::Value>, Error> {
|
||||
self.execute_script(isolate, name, ModuleCode::from_static(source_code))
|
||||
}
|
||||
|
||||
/// Executes traditional JavaScript code (traditional = not ES modules) in the
|
||||
/// realm's context.
|
||||
///
|
||||
/// For info on the [`v8::Isolate`] parameter, check [`JsRealm#panics`].
|
||||
///
|
||||
/// The `name` parameter can be a filepath or any other string. E.g.:
|
||||
///
|
||||
/// - "/some/file/path.js"
|
||||
/// - "<anon>"
|
||||
/// - "[native code]"
|
||||
///
|
||||
/// The same `name` value can be used for multiple executions.
|
||||
///
|
||||
/// `Error` can usually be downcast to `JsError`.
|
||||
pub fn execute_script(
|
||||
&self,
|
||||
isolate: &mut v8::Isolate,
|
||||
name: &'static str,
|
||||
source_code: ModuleCode,
|
||||
) -> Result<v8::Global<v8::Value>, Error> {
|
||||
let scope = &mut self.0.handle_scope(isolate);
|
||||
|
||||
let source = Self::string_from_code(scope, &source_code).unwrap();
|
||||
debug_assert!(name.is_ascii());
|
||||
let name =
|
||||
v8::String::new_external_onebyte_static(scope, name.as_bytes()).unwrap();
|
||||
let origin = bindings::script_origin(scope, name);
|
||||
|
||||
let tc_scope = &mut v8::TryCatch::new(scope);
|
||||
|
||||
let script = match v8::Script::compile(tc_scope, source, Some(&origin)) {
|
||||
Some(script) => script,
|
||||
None => {
|
||||
let exception = tc_scope.exception().unwrap();
|
||||
return exception_to_err_result(tc_scope, exception, false);
|
||||
}
|
||||
};
|
||||
|
||||
match script.run(tc_scope) {
|
||||
Some(value) => {
|
||||
let value_handle = v8::Global::new(tc_scope, value);
|
||||
Ok(value_handle)
|
||||
}
|
||||
None => {
|
||||
assert!(tc_scope.has_caught());
|
||||
let exception = tc_scope.exception().unwrap();
|
||||
exception_to_err_result(tc_scope, exception, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(andreubotella): `mod_evaluate`, `load_main_module`, `load_side_module`
|
||||
}
|
||||
|
||||
impl Drop for JsRealm {
|
||||
fn drop(&mut self) {
|
||||
// Don't do anything special with the global realm
|
||||
if self.0.is_global {
|
||||
return;
|
||||
}
|
||||
|
||||
// There's us and there's the runtime
|
||||
if Rc::strong_count(&self.0.context) == 2 {
|
||||
self
|
||||
.0
|
||||
.runtime_state
|
||||
.borrow_mut()
|
||||
.remove_realm(&self.0.context);
|
||||
assert_eq!(Rc::strong_count(&self.0.context), 1);
|
||||
self.0.clone().destroy();
|
||||
assert_eq!(Rc::strong_count(&self.0.context_state), 1);
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -1,34 +0,0 @@
|
|||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
||||
mod bindings;
|
||||
mod jsrealm;
|
||||
mod jsruntime;
|
||||
#[doc(hidden)]
|
||||
pub mod ops;
|
||||
mod snapshot_util;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
pub const V8_WRAPPER_TYPE_INDEX: i32 = 0;
|
||||
pub const V8_WRAPPER_OBJECT_INDEX: i32 = 1;
|
||||
|
||||
pub(crate) use jsrealm::ContextState;
|
||||
pub use jsrealm::JsRealm;
|
||||
pub use jsruntime::CompiledWasmModuleStore;
|
||||
pub use jsruntime::CrossIsolateStore;
|
||||
pub(crate) use jsruntime::InitMode;
|
||||
pub use jsruntime::JsRuntime;
|
||||
pub use jsruntime::JsRuntimeForSnapshot;
|
||||
pub use jsruntime::JsRuntimeState;
|
||||
pub use jsruntime::RuntimeOptions;
|
||||
pub use jsruntime::RuntimeSnapshotOptions;
|
||||
pub use jsruntime::SharedArrayBufferStore;
|
||||
pub use jsruntime::Snapshot;
|
||||
pub use snapshot_util::create_snapshot;
|
||||
pub use snapshot_util::get_js_files;
|
||||
pub use snapshot_util::CreateSnapshotOptions;
|
||||
pub use snapshot_util::CreateSnapshotOutput;
|
||||
pub use snapshot_util::FilterFn;
|
||||
pub(crate) use snapshot_util::SnapshottedData;
|
||||
|
||||
pub use bindings::script_origin;
|
|
@ -1,634 +0,0 @@
|
|||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
||||
use crate::ops::*;
|
||||
use crate::OpResult;
|
||||
use crate::PromiseId;
|
||||
use anyhow::Error;
|
||||
use futures::future::Either;
|
||||
use futures::future::Future;
|
||||
use futures::future::FutureExt;
|
||||
use futures::task::noop_waker_ref;
|
||||
use std::borrow::Cow;
|
||||
use std::cell::RefCell;
|
||||
use std::future::ready;
|
||||
use std::mem::MaybeUninit;
|
||||
use std::option::Option;
|
||||
use std::task::Context;
|
||||
use std::task::Poll;
|
||||
|
||||
#[inline]
|
||||
pub fn queue_fast_async_op<R: serde::Serialize + 'static>(
|
||||
ctx: &OpCtx,
|
||||
promise_id: PromiseId,
|
||||
op: impl Future<Output = Result<R, Error>> + 'static,
|
||||
) {
|
||||
let get_class = {
|
||||
let state = RefCell::borrow(&ctx.state);
|
||||
state.tracker.track_async(ctx.id);
|
||||
state.get_error_class_fn
|
||||
};
|
||||
let fut = op.map(|result| crate::_ops::to_op_result(get_class, result));
|
||||
ctx
|
||||
.context_state
|
||||
.borrow_mut()
|
||||
.pending_ops
|
||||
.spawn(OpCall::new(ctx, promise_id, fut));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn map_async_op1<R: serde::Serialize + 'static>(
|
||||
ctx: &OpCtx,
|
||||
op: impl Future<Output = Result<R, Error>> + 'static,
|
||||
) -> impl Future<Output = OpResult> {
|
||||
let get_class = {
|
||||
let state = RefCell::borrow(&ctx.state);
|
||||
state.tracker.track_async(ctx.id);
|
||||
state.get_error_class_fn
|
||||
};
|
||||
|
||||
op.map(|res| crate::_ops::to_op_result(get_class, res))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn map_async_op2<R: serde::Serialize + 'static>(
|
||||
ctx: &OpCtx,
|
||||
op: impl Future<Output = R> + 'static,
|
||||
) -> impl Future<Output = OpResult> {
|
||||
let state = RefCell::borrow(&ctx.state);
|
||||
state.tracker.track_async(ctx.id);
|
||||
|
||||
op.map(|res| OpResult::Ok(res.into()))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn map_async_op3<R: serde::Serialize + 'static>(
|
||||
ctx: &OpCtx,
|
||||
op: Result<impl Future<Output = Result<R, Error>> + 'static, Error>,
|
||||
) -> impl Future<Output = OpResult> {
|
||||
let get_class = {
|
||||
let state = RefCell::borrow(&ctx.state);
|
||||
state.tracker.track_async(ctx.id);
|
||||
state.get_error_class_fn
|
||||
};
|
||||
|
||||
match op {
|
||||
Err(err) => {
|
||||
Either::Left(ready(OpResult::Err(OpError::new(get_class, err))))
|
||||
}
|
||||
Ok(fut) => {
|
||||
Either::Right(fut.map(|res| crate::_ops::to_op_result(get_class, res)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn map_async_op4<R: serde::Serialize + 'static>(
|
||||
ctx: &OpCtx,
|
||||
op: Result<impl Future<Output = R> + 'static, Error>,
|
||||
) -> impl Future<Output = OpResult> {
|
||||
let get_class = {
|
||||
let state = RefCell::borrow(&ctx.state);
|
||||
state.tracker.track_async(ctx.id);
|
||||
state.get_error_class_fn
|
||||
};
|
||||
|
||||
match op {
|
||||
Err(err) => {
|
||||
Either::Left(ready(OpResult::Err(OpError::new(get_class, err))))
|
||||
}
|
||||
Ok(fut) => Either::Right(fut.map(|r| OpResult::Ok(r.into()))),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn queue_async_op<'s>(
|
||||
ctx: &OpCtx,
|
||||
scope: &'s mut v8::HandleScope,
|
||||
deferred: bool,
|
||||
promise_id: PromiseId,
|
||||
op: impl Future<Output = OpResult> + 'static,
|
||||
) -> Option<v8::Local<'s, v8::Value>> {
|
||||
// An op's realm (as given by `OpCtx::realm_idx`) must match the realm in
|
||||
// which it is invoked. Otherwise, we might have cross-realm object exposure.
|
||||
// deno_core doesn't currently support such exposure, even though embedders
|
||||
// can cause them, so we panic in debug mode (since the check is expensive).
|
||||
// TODO(mmastrac): Restore this
|
||||
// debug_assert_eq!(
|
||||
// runtime_state.borrow().context(ctx.realm_idx as usize, scope),
|
||||
// Some(scope.get_current_context())
|
||||
// );
|
||||
|
||||
let id = ctx.id;
|
||||
|
||||
// TODO(mmastrac): We have to poll every future here because that assumption is baked into a large number
|
||||
// of ops. If we can figure out a way around this, we can remove this call to boxed_local and save a malloc per future.
|
||||
let mut pinned = op.map(move |res| (promise_id, id, res)).boxed_local();
|
||||
|
||||
match pinned.poll_unpin(&mut Context::from_waker(noop_waker_ref())) {
|
||||
Poll::Pending => {}
|
||||
Poll::Ready(mut res) => {
|
||||
if deferred {
|
||||
ctx.context_state.borrow_mut().pending_ops.spawn(ready(res));
|
||||
return None;
|
||||
} else {
|
||||
ctx.state.borrow_mut().tracker.track_async_completed(ctx.id);
|
||||
return Some(res.2.to_v8(scope).unwrap());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ctx.context_state.borrow_mut().pending_ops.spawn(pinned);
|
||||
None
|
||||
}
|
||||
|
||||
macro_rules! try_number {
|
||||
($n:ident $type:ident $is:ident) => {
|
||||
if $n.$is() {
|
||||
// SAFETY: v8 handles can be transmuted
|
||||
let n: &v8::Uint32 = unsafe { std::mem::transmute($n) };
|
||||
return n.value() as _;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub fn to_u32(number: &v8::Value) -> u32 {
|
||||
try_number!(number Uint32 is_uint32);
|
||||
try_number!(number Int32 is_int32);
|
||||
try_number!(number Number is_number);
|
||||
if number.is_big_int() {
|
||||
// SAFETY: v8 handles can be transmuted
|
||||
let n: &v8::BigInt = unsafe { std::mem::transmute(number) };
|
||||
return n.u64_value().0 as _;
|
||||
}
|
||||
0
|
||||
}
|
||||
|
||||
pub fn to_i32(number: &v8::Value) -> i32 {
|
||||
try_number!(number Uint32 is_uint32);
|
||||
try_number!(number Int32 is_int32);
|
||||
try_number!(number Number is_number);
|
||||
if number.is_big_int() {
|
||||
// SAFETY: v8 handles can be transmuted
|
||||
let n: &v8::BigInt = unsafe { std::mem::transmute(number) };
|
||||
return n.i64_value().0 as _;
|
||||
}
|
||||
0
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn to_u64(number: &v8::Value) -> u32 {
|
||||
try_number!(number Uint32 is_uint32);
|
||||
try_number!(number Int32 is_int32);
|
||||
try_number!(number Number is_number);
|
||||
if number.is_big_int() {
|
||||
// SAFETY: v8 handles can be transmuted
|
||||
let n: &v8::BigInt = unsafe { std::mem::transmute(number) };
|
||||
return n.u64_value().0 as _;
|
||||
}
|
||||
0
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn to_i64(number: &v8::Value) -> i32 {
|
||||
try_number!(number Uint32 is_uint32);
|
||||
try_number!(number Int32 is_int32);
|
||||
try_number!(number Number is_number);
|
||||
if number.is_big_int() {
|
||||
// SAFETY: v8 handles can be transmuted
|
||||
let n: &v8::BigInt = unsafe { std::mem::transmute(number) };
|
||||
return n.i64_value().0 as _;
|
||||
}
|
||||
0
|
||||
}
|
||||
|
||||
/// Expands `inbuf` to `outbuf`, assuming that `outbuf` has at least 2x `input_length`.
|
||||
#[inline(always)]
|
||||
unsafe fn latin1_to_utf8(
|
||||
input_length: usize,
|
||||
inbuf: *const u8,
|
||||
outbuf: *mut u8,
|
||||
) -> usize {
|
||||
let mut output = 0;
|
||||
let mut input = 0;
|
||||
while input < input_length {
|
||||
let char = *(inbuf.add(input));
|
||||
if char < 0x80 {
|
||||
*(outbuf.add(output)) = char;
|
||||
output += 1;
|
||||
} else {
|
||||
// Top two bits
|
||||
*(outbuf.add(output)) = (char >> 6) | 0b1100_0000;
|
||||
// Bottom six bits
|
||||
*(outbuf.add(output + 1)) = (char & 0b0011_1111) | 0b1000_0000;
|
||||
output += 2;
|
||||
}
|
||||
input += 1;
|
||||
}
|
||||
output
|
||||
}
|
||||
|
||||
/// Converts a [`v8::fast_api::FastApiOneByteString`] to either an owned string, or a borrowed string, depending on whether it fits into the
|
||||
/// provided buffer.
|
||||
pub fn to_str_ptr<'a, const N: usize>(
|
||||
string: &mut v8::fast_api::FastApiOneByteString,
|
||||
buffer: &'a mut [MaybeUninit<u8>; N],
|
||||
) -> Cow<'a, str> {
|
||||
let input_buf = string.as_bytes();
|
||||
let input_len = input_buf.len();
|
||||
let output_len = buffer.len();
|
||||
|
||||
// We know that this string is full of either one or two-byte UTF-8 chars, so if it's < 1/2 of N we
|
||||
// can skip the ASCII check and just start copying.
|
||||
if input_len < N / 2 {
|
||||
debug_assert!(output_len >= input_len * 2);
|
||||
let buffer = buffer.as_mut_ptr() as *mut u8;
|
||||
|
||||
let written =
|
||||
// SAFETY: We checked that buffer is at least 2x the size of input_buf
|
||||
unsafe { latin1_to_utf8(input_buf.len(), input_buf.as_ptr(), buffer) };
|
||||
|
||||
debug_assert!(written <= output_len);
|
||||
|
||||
let slice = std::ptr::slice_from_raw_parts(buffer, written);
|
||||
// SAFETY: We know it's valid UTF-8, so make a string
|
||||
Cow::Borrowed(unsafe { std::str::from_utf8_unchecked(&*slice) })
|
||||
} else {
|
||||
// TODO(mmastrac): We could be smarter here about not allocating
|
||||
Cow::Owned(to_string_ptr(string))
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts a [`v8::fast_api::FastApiOneByteString`] to an owned string. May over-allocate to avoid
|
||||
/// re-allocation.
|
||||
pub fn to_string_ptr(
|
||||
string: &mut v8::fast_api::FastApiOneByteString,
|
||||
) -> String {
|
||||
let input_buf = string.as_bytes();
|
||||
let capacity = input_buf.len() * 2;
|
||||
|
||||
// SAFETY: We're allocating a buffer of 2x the input size, writing valid UTF-8, then turning that into a string
|
||||
unsafe {
|
||||
// Create an uninitialized buffer of `capacity` bytes. We need to be careful here to avoid
|
||||
// accidentally creating a slice of u8 which would be invalid.
|
||||
let layout = std::alloc::Layout::from_size_align(capacity, 1).unwrap();
|
||||
let out = std::alloc::alloc(layout);
|
||||
|
||||
let written = latin1_to_utf8(input_buf.len(), input_buf.as_ptr(), out);
|
||||
|
||||
debug_assert!(written <= capacity);
|
||||
// We know it's valid UTF-8, so make a string
|
||||
String::from_raw_parts(out, written, capacity)
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts a [`v8::String`] to either an owned string, or a borrowed string, depending on whether it fits into the
|
||||
/// provided buffer.
|
||||
#[inline(always)]
|
||||
pub fn to_str<'a, const N: usize>(
|
||||
scope: &mut v8::Isolate,
|
||||
string: &v8::Value,
|
||||
buffer: &'a mut [MaybeUninit<u8>; N],
|
||||
) -> Cow<'a, str> {
|
||||
if !string.is_string() {
|
||||
return Cow::Borrowed("");
|
||||
}
|
||||
|
||||
// SAFETY: We checked is_string above
|
||||
let string: &v8::String = unsafe { std::mem::transmute(string) };
|
||||
|
||||
string.to_rust_cow_lossy(scope, buffer)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::error::generic_error;
|
||||
use crate::error::AnyError;
|
||||
use crate::error::JsError;
|
||||
use crate::FastString;
|
||||
use crate::JsRuntime;
|
||||
use crate::RuntimeOptions;
|
||||
use deno_ops::op2;
|
||||
use std::borrow::Cow;
|
||||
use std::cell::Cell;
|
||||
|
||||
crate::extension!(
|
||||
testing,
|
||||
ops = [
|
||||
op_test_fail,
|
||||
op_test_add,
|
||||
op_test_add_option,
|
||||
op_test_result_void_switch,
|
||||
op_test_result_void_ok,
|
||||
op_test_result_void_err,
|
||||
op_test_result_primitive_ok,
|
||||
op_test_result_primitive_err,
|
||||
op_test_string_owned,
|
||||
op_test_string_ref,
|
||||
op_test_string_cow,
|
||||
op_test_string_roundtrip_char,
|
||||
op_test_string_return,
|
||||
op_test_string_option_return,
|
||||
op_test_string_roundtrip,
|
||||
op_test_generics<String>,
|
||||
]
|
||||
);
|
||||
|
||||
thread_local! {
|
||||
static FAIL: Cell<bool> = Cell::new(false)
|
||||
}
|
||||
|
||||
#[op2(core, fast)]
|
||||
pub fn op_test_fail() {
|
||||
FAIL.with(|b| b.set(true))
|
||||
}
|
||||
|
||||
/// Run a test for a single op.
|
||||
fn run_test2(repeat: usize, op: &str, test: &str) -> Result<(), AnyError> {
|
||||
let mut runtime = JsRuntime::new(RuntimeOptions {
|
||||
extensions: vec![testing::init_ops_and_esm()],
|
||||
..Default::default()
|
||||
});
|
||||
runtime
|
||||
.execute_script(
|
||||
"",
|
||||
FastString::Owned(
|
||||
format!(
|
||||
r"
|
||||
const {{ op_test_fail, {op} }} = Deno.core.ensureFastOps();
|
||||
function assert(b) {{
|
||||
if (!b) {{
|
||||
op_test_fail();
|
||||
}}
|
||||
}}
|
||||
"
|
||||
)
|
||||
.into(),
|
||||
),
|
||||
)
|
||||
.unwrap();
|
||||
FAIL.with(|b| b.set(false));
|
||||
runtime.execute_script(
|
||||
"",
|
||||
FastString::Owned(
|
||||
format!(
|
||||
r"
|
||||
for (let __index__ = 0; __index__ < {repeat}; __index__++) {{
|
||||
{test}
|
||||
}}
|
||||
"
|
||||
)
|
||||
.into(),
|
||||
),
|
||||
)?;
|
||||
if FAIL.with(|b| b.get()) {
|
||||
Err(generic_error(format!("{op} test failed ({test})")))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
pub async fn test_op_fail() {
|
||||
assert!(run_test2(1, "", "assert(false)").is_err());
|
||||
}
|
||||
|
||||
#[op2(core, fast)]
|
||||
pub fn op_test_add(a: u32, b: u32) -> u32 {
|
||||
a + b
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
pub async fn test_op_add() -> Result<(), Box<dyn std::error::Error>> {
|
||||
Ok(run_test2(
|
||||
10000,
|
||||
"op_test_add",
|
||||
"assert(op_test_add(1, 11) == 12)",
|
||||
)?)
|
||||
}
|
||||
|
||||
#[op2(core)]
|
||||
pub fn op_test_add_option(a: u32, b: Option<u32>) -> u32 {
|
||||
a + b.unwrap_or(100)
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
pub async fn test_op_add_option() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// This isn't fast, so we don't repeat it
|
||||
run_test2(
|
||||
1,
|
||||
"op_test_add_option",
|
||||
"assert(op_test_add_option(1, 11) == 12)",
|
||||
)?;
|
||||
run_test2(
|
||||
1,
|
||||
"op_test_add_option",
|
||||
"assert(op_test_add_option(1, null) == 101)",
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
static RETURN_COUNT: Cell<usize> = Cell::new(0);
|
||||
}
|
||||
|
||||
#[op2(core, fast)]
|
||||
pub fn op_test_result_void_switch() -> Result<(), AnyError> {
|
||||
let count = RETURN_COUNT.with(|count| {
|
||||
let new = count.get() + 1;
|
||||
count.set(new);
|
||||
new
|
||||
});
|
||||
if count > 5000 {
|
||||
Err(generic_error("failed!!!"))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[op2(core, fast)]
|
||||
pub fn op_test_result_void_err() -> Result<(), AnyError> {
|
||||
Err(generic_error("failed!!!"))
|
||||
}
|
||||
|
||||
#[op2(core, fast)]
|
||||
pub fn op_test_result_void_ok() -> Result<(), AnyError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
pub async fn test_op_result_void() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Test the non-switching kinds
|
||||
run_test2(
|
||||
10000,
|
||||
"op_test_result_void_err",
|
||||
"try { op_test_result_void_err(); assert(false) } catch (e) {}",
|
||||
)?;
|
||||
run_test2(10000, "op_test_result_void_ok", "op_test_result_void_ok()")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
pub async fn test_op_result_void_switch(
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
RETURN_COUNT.with(|count| count.set(0));
|
||||
let err = run_test2(
|
||||
10000,
|
||||
"op_test_result_void_switch",
|
||||
"op_test_result_void_switch();",
|
||||
)
|
||||
.expect_err("Expected this to fail");
|
||||
let js_err = err.downcast::<JsError>().unwrap();
|
||||
assert_eq!(js_err.message, Some("failed!!!".into()));
|
||||
assert_eq!(RETURN_COUNT.with(|count| count.get()), 5001);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[op2(core, fast)]
|
||||
pub fn op_test_result_primitive_err() -> Result<u32, AnyError> {
|
||||
Err(generic_error("failed!!!"))
|
||||
}
|
||||
|
||||
#[op2(core, fast)]
|
||||
pub fn op_test_result_primitive_ok() -> Result<u32, AnyError> {
|
||||
Ok(123)
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
pub async fn test_op_result_primitive(
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
run_test2(
|
||||
10000,
|
||||
"op_test_result_primitive_err",
|
||||
"try { op_test_result_primitive_err(); assert(false) } catch (e) {}",
|
||||
)?;
|
||||
run_test2(
|
||||
10000,
|
||||
"op_test_result_primitive_ok",
|
||||
"op_test_result_primitive_ok()",
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[op2(core, fast)]
|
||||
pub fn op_test_string_owned(#[string] s: String) -> u32 {
|
||||
s.len() as _
|
||||
}
|
||||
|
||||
#[op2(core, fast)]
|
||||
pub fn op_test_string_ref(#[string] s: &str) -> u32 {
|
||||
s.len() as _
|
||||
}
|
||||
|
||||
#[op2(core, fast)]
|
||||
pub fn op_test_string_cow(#[string] s: Cow<str>) -> u32 {
|
||||
s.len() as _
|
||||
}
|
||||
|
||||
#[op2(core, fast)]
|
||||
pub fn op_test_string_roundtrip_char(#[string] s: Cow<str>) -> u32 {
|
||||
s.chars().next().unwrap() as u32
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
pub async fn test_op_strings() -> Result<(), Box<dyn std::error::Error>> {
|
||||
for op in [
|
||||
"op_test_string_owned",
|
||||
"op_test_string_cow",
|
||||
"op_test_string_ref",
|
||||
] {
|
||||
for (len, str) in [
|
||||
// ASCII
|
||||
(3, "'abc'"),
|
||||
// Latin-1 (one byte but two UTF-8 chars)
|
||||
(2, "'\\u00a0'"),
|
||||
// ASCII
|
||||
(10000, "'a'.repeat(10000)"),
|
||||
// Latin-1
|
||||
(20000, "'\\u00a0'.repeat(10000)"),
|
||||
// 4-byte UTF-8 emoji (1F995 = 🦕)
|
||||
(40000, "'\\u{1F995}'.repeat(10000)"),
|
||||
] {
|
||||
let test = format!("assert({op}({str}) == {len})");
|
||||
run_test2(10000, op, &test)?;
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that we're correctly encoding UTF-8
|
||||
run_test2(
|
||||
10000,
|
||||
"op_test_string_roundtrip_char",
|
||||
"assert(op_test_string_roundtrip_char('\\u00a0') == 0xa0)",
|
||||
)?;
|
||||
run_test2(
|
||||
10000,
|
||||
"op_test_string_roundtrip_char",
|
||||
"assert(op_test_string_roundtrip_char('\\u00ff') == 0xff)",
|
||||
)?;
|
||||
run_test2(
|
||||
10000,
|
||||
"op_test_string_roundtrip_char",
|
||||
"assert(op_test_string_roundtrip_char('\\u0080') == 0x80)",
|
||||
)?;
|
||||
run_test2(
|
||||
10000,
|
||||
"op_test_string_roundtrip_char",
|
||||
"assert(op_test_string_roundtrip_char('\\u0100') == 0x100)",
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[op2(core)]
|
||||
#[string]
|
||||
pub fn op_test_string_return(
|
||||
#[string] a: Cow<str>,
|
||||
#[string] b: Cow<str>,
|
||||
) -> String {
|
||||
(a + b).to_string()
|
||||
}
|
||||
|
||||
#[op2(core)]
|
||||
#[string]
|
||||
pub fn op_test_string_option_return(
|
||||
#[string] a: Cow<str>,
|
||||
#[string] b: Cow<str>,
|
||||
) -> Option<String> {
|
||||
if a == "none" {
|
||||
return None;
|
||||
}
|
||||
Some((a + b).to_string())
|
||||
}
|
||||
|
||||
#[op2(core)]
|
||||
#[string]
|
||||
pub fn op_test_string_roundtrip(#[string] s: String) -> String {
|
||||
s
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
pub async fn test_op_string_returns() -> Result<(), Box<dyn std::error::Error>>
|
||||
{
|
||||
run_test2(
|
||||
1,
|
||||
"op_test_string_return",
|
||||
"assert(op_test_string_return('a', 'b') == 'ab')",
|
||||
)?;
|
||||
run_test2(
|
||||
1,
|
||||
"op_test_string_option_return",
|
||||
"assert(op_test_string_option_return('a', 'b') == 'ab')",
|
||||
)?;
|
||||
run_test2(
|
||||
1,
|
||||
"op_test_string_option_return",
|
||||
"assert(op_test_string_option_return('none', 'b') == null)",
|
||||
)?;
|
||||
run_test2(
|
||||
1,
|
||||
"op_test_string_roundtrip",
|
||||
"assert(op_test_string_roundtrip('\\u0080\\u00a0\\u00ff') == '\\u0080\\u00a0\\u00ff')",
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// We don't actually test this one -- we just want it to compile
|
||||
#[op2(core, fast)]
|
||||
pub fn op_test_generics<T: Clone>() {}
|
||||
}
|
|
@ -1,74 +0,0 @@
|
|||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
||||
"use strict";
|
||||
|
||||
function assert(cond) {
|
||||
if (!cond) {
|
||||
throw Error("assert");
|
||||
}
|
||||
}
|
||||
|
||||
function assertArrayEquals(a1, a2) {
|
||||
if (a1.length !== a2.length) throw Error("assert");
|
||||
|
||||
for (const index in a1) {
|
||||
if (a1[index] !== a2[index]) {
|
||||
throw Error(`assert: (index ${index}) ${a1[index]} !== ${a2[index]}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function main() {
|
||||
const emptyString = "";
|
||||
const emptyStringSerialized = [255, 15, 34, 0];
|
||||
assertArrayEquals(
|
||||
Deno.core.ops.op_serialize(emptyString),
|
||||
emptyStringSerialized,
|
||||
);
|
||||
assert(
|
||||
Deno.core.ops.op_deserialize(
|
||||
new Uint8Array(emptyStringSerialized),
|
||||
) ===
|
||||
emptyString,
|
||||
);
|
||||
|
||||
const primitiveValueArray = ["test", "a", null, undefined];
|
||||
// deno-fmt-ignore
|
||||
const primitiveValueArraySerialized = [
|
||||
255, 15, 65, 4, 34, 4, 116, 101, 115, 116,
|
||||
34, 1, 97, 48, 95, 36, 0, 4,
|
||||
];
|
||||
assertArrayEquals(
|
||||
Deno.core.ops.op_serialize(primitiveValueArray),
|
||||
primitiveValueArraySerialized,
|
||||
);
|
||||
|
||||
assertArrayEquals(
|
||||
Deno.core.ops.op_deserialize(
|
||||
new Uint8Array(primitiveValueArraySerialized),
|
||||
),
|
||||
primitiveValueArray,
|
||||
);
|
||||
|
||||
const circularObject = { test: null, test2: "dd", test3: "aa" };
|
||||
circularObject.test = circularObject;
|
||||
// deno-fmt-ignore
|
||||
const circularObjectSerialized = [
|
||||
255, 15, 111, 34, 4, 116, 101, 115,
|
||||
116, 94, 0, 34, 5, 116, 101, 115,
|
||||
116, 50, 34, 2, 100, 100, 34, 5,
|
||||
116, 101, 115, 116, 51, 34, 2, 97,
|
||||
97, 123, 3,
|
||||
];
|
||||
|
||||
assertArrayEquals(
|
||||
Deno.core.ops.op_serialize(circularObject),
|
||||
circularObjectSerialized,
|
||||
);
|
||||
|
||||
const deserializedCircularObject = Deno.core.ops.op_deserialize(
|
||||
new Uint8Array(circularObjectSerialized),
|
||||
);
|
||||
assert(deserializedCircularObject.test == deserializedCircularObject);
|
||||
}
|
||||
|
||||
main();
|
|
@ -1,259 +0,0 @@
|
|||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
||||
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
use std::time::Instant;
|
||||
|
||||
use crate::runtime::jsruntime::BUILTIN_SOURCES;
|
||||
use crate::runtime::RuntimeSnapshotOptions;
|
||||
use crate::ExtModuleLoaderCb;
|
||||
use crate::Extension;
|
||||
use crate::ExtensionFileSourceCode;
|
||||
use crate::JsRuntimeForSnapshot;
|
||||
use crate::RuntimeOptions;
|
||||
use crate::Snapshot;
|
||||
|
||||
pub type CompressionCb = dyn Fn(&mut Vec<u8>, &[u8]);
|
||||
|
||||
pub struct CreateSnapshotOptions {
|
||||
pub cargo_manifest_dir: &'static str,
|
||||
pub snapshot_path: PathBuf,
|
||||
pub startup_snapshot: Option<Snapshot>,
|
||||
pub extensions: Vec<Extension>,
|
||||
pub compression_cb: Option<Box<CompressionCb>>,
|
||||
pub snapshot_module_load_cb: Option<ExtModuleLoaderCb>,
|
||||
}
|
||||
|
||||
pub struct CreateSnapshotOutput {
|
||||
/// Any files marked as LoadedFromFsDuringSnapshot are collected here and should be
|
||||
/// printed as 'cargo:rerun-if-changed' lines from your build script.
|
||||
pub files_loaded_during_snapshot: Vec<PathBuf>,
|
||||
}
|
||||
|
||||
#[must_use = "The files listed by create_snapshot should be printed as 'cargo:rerun-if-changed' lines"]
|
||||
pub fn create_snapshot(
|
||||
create_snapshot_options: CreateSnapshotOptions,
|
||||
) -> CreateSnapshotOutput {
|
||||
let mut mark = Instant::now();
|
||||
|
||||
let js_runtime = JsRuntimeForSnapshot::new(
|
||||
RuntimeOptions {
|
||||
startup_snapshot: create_snapshot_options.startup_snapshot,
|
||||
extensions: create_snapshot_options.extensions,
|
||||
..Default::default()
|
||||
},
|
||||
RuntimeSnapshotOptions {
|
||||
snapshot_module_load_cb: create_snapshot_options.snapshot_module_load_cb,
|
||||
},
|
||||
);
|
||||
println!(
|
||||
"JsRuntime for snapshot prepared, took {:#?} ({})",
|
||||
Instant::now().saturating_duration_since(mark),
|
||||
create_snapshot_options.snapshot_path.display()
|
||||
);
|
||||
mark = Instant::now();
|
||||
|
||||
let mut files_loaded_during_snapshot = vec![];
|
||||
for source in &*BUILTIN_SOURCES {
|
||||
if let ExtensionFileSourceCode::LoadedFromFsDuringSnapshot(path) =
|
||||
&source.code
|
||||
{
|
||||
files_loaded_during_snapshot.push(path.clone());
|
||||
}
|
||||
}
|
||||
for source in js_runtime
|
||||
.extensions()
|
||||
.iter()
|
||||
.flat_map(|e| vec![e.get_esm_sources(), e.get_js_sources()])
|
||||
.flatten()
|
||||
{
|
||||
if let ExtensionFileSourceCode::LoadedFromFsDuringSnapshot(path) =
|
||||
&source.code
|
||||
{
|
||||
files_loaded_during_snapshot.push(path.clone());
|
||||
}
|
||||
}
|
||||
|
||||
let snapshot = js_runtime.snapshot();
|
||||
let snapshot_slice: &[u8] = &snapshot;
|
||||
println!(
|
||||
"Snapshot size: {}, took {:#?} ({})",
|
||||
snapshot_slice.len(),
|
||||
Instant::now().saturating_duration_since(mark),
|
||||
create_snapshot_options.snapshot_path.display()
|
||||
);
|
||||
mark = Instant::now();
|
||||
|
||||
let maybe_compressed_snapshot: Box<dyn AsRef<[u8]>> =
|
||||
if let Some(compression_cb) = create_snapshot_options.compression_cb {
|
||||
let mut vec = vec![];
|
||||
|
||||
vec.extend_from_slice(
|
||||
&u32::try_from(snapshot.len())
|
||||
.expect("snapshot larger than 4gb")
|
||||
.to_le_bytes(),
|
||||
);
|
||||
|
||||
(compression_cb)(&mut vec, snapshot_slice);
|
||||
|
||||
println!(
|
||||
"Snapshot compressed size: {}, took {:#?} ({})",
|
||||
vec.len(),
|
||||
Instant::now().saturating_duration_since(mark),
|
||||
create_snapshot_options.snapshot_path.display()
|
||||
);
|
||||
mark = std::time::Instant::now();
|
||||
|
||||
Box::new(vec)
|
||||
} else {
|
||||
Box::new(snapshot_slice)
|
||||
};
|
||||
|
||||
std::fs::write(
|
||||
&create_snapshot_options.snapshot_path,
|
||||
&*maybe_compressed_snapshot,
|
||||
)
|
||||
.unwrap();
|
||||
println!(
|
||||
"Snapshot written, took: {:#?} ({})",
|
||||
Instant::now().saturating_duration_since(mark),
|
||||
create_snapshot_options.snapshot_path.display(),
|
||||
);
|
||||
CreateSnapshotOutput {
|
||||
files_loaded_during_snapshot,
|
||||
}
|
||||
}
|
||||
|
||||
pub type FilterFn = Box<dyn Fn(&PathBuf) -> bool>;
|
||||
|
||||
pub fn get_js_files(
|
||||
cargo_manifest_dir: &'static str,
|
||||
directory: &str,
|
||||
filter: Option<FilterFn>,
|
||||
) -> Vec<PathBuf> {
|
||||
let manifest_dir = Path::new(cargo_manifest_dir);
|
||||
let mut js_files = std::fs::read_dir(directory)
|
||||
.unwrap()
|
||||
.map(|dir_entry| {
|
||||
let file = dir_entry.unwrap();
|
||||
manifest_dir.join(file.path())
|
||||
})
|
||||
.filter(|path| {
|
||||
path.extension().unwrap_or_default() == "js"
|
||||
&& filter.as_ref().map(|filter| filter(path)).unwrap_or(true)
|
||||
})
|
||||
.collect::<Vec<PathBuf>>();
|
||||
js_files.sort();
|
||||
js_files
|
||||
}
|
||||
|
||||
fn data_error_to_panic(err: v8::DataError) -> ! {
|
||||
match err {
|
||||
v8::DataError::BadType { actual, expected } => {
|
||||
panic!(
|
||||
"Invalid type for snapshot data: expected {expected}, got {actual}"
|
||||
);
|
||||
}
|
||||
v8::DataError::NoData { expected } => {
|
||||
panic!("No data for snapshot data: expected {expected}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct SnapshottedData {
|
||||
pub module_map_data: v8::Global<v8::Array>,
|
||||
pub module_handles: Vec<v8::Global<v8::Module>>,
|
||||
}
|
||||
|
||||
static MODULE_MAP_CONTEXT_DATA_INDEX: usize = 0;
|
||||
|
||||
pub(crate) fn get_snapshotted_data(
|
||||
scope: &mut v8::HandleScope<()>,
|
||||
context: v8::Local<v8::Context>,
|
||||
) -> SnapshottedData {
|
||||
let mut scope = v8::ContextScope::new(scope, context);
|
||||
|
||||
// The 0th element is the module map itself, followed by X number of module
|
||||
// handles. We need to deserialize the "next_module_id" field from the
|
||||
// map to see how many module handles we expect.
|
||||
let result = scope.get_context_data_from_snapshot_once::<v8::Array>(
|
||||
MODULE_MAP_CONTEXT_DATA_INDEX,
|
||||
);
|
||||
|
||||
let val = match result {
|
||||
Ok(v) => v,
|
||||
Err(err) => data_error_to_panic(err),
|
||||
};
|
||||
|
||||
let next_module_id = {
|
||||
let info_data: v8::Local<v8::Array> =
|
||||
val.get_index(&mut scope, 1).unwrap().try_into().unwrap();
|
||||
info_data.length()
|
||||
};
|
||||
|
||||
// Over allocate so executing a few scripts doesn't have to resize this vec.
|
||||
let mut module_handles = Vec::with_capacity(next_module_id as usize + 16);
|
||||
for i in 1..=next_module_id {
|
||||
match scope.get_context_data_from_snapshot_once::<v8::Module>(i as usize) {
|
||||
Ok(val) => {
|
||||
let module_global = v8::Global::new(&mut scope, val);
|
||||
module_handles.push(module_global);
|
||||
}
|
||||
Err(err) => data_error_to_panic(err),
|
||||
}
|
||||
}
|
||||
|
||||
SnapshottedData {
|
||||
module_map_data: v8::Global::new(&mut scope, val),
|
||||
module_handles,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn set_snapshotted_data(
|
||||
scope: &mut v8::HandleScope<()>,
|
||||
context: v8::Global<v8::Context>,
|
||||
snapshotted_data: SnapshottedData,
|
||||
) {
|
||||
let local_context = v8::Local::new(scope, context);
|
||||
let local_data = v8::Local::new(scope, snapshotted_data.module_map_data);
|
||||
let offset = scope.add_context_data(local_context, local_data);
|
||||
assert_eq!(offset, MODULE_MAP_CONTEXT_DATA_INDEX);
|
||||
|
||||
for (index, handle) in snapshotted_data.module_handles.into_iter().enumerate()
|
||||
{
|
||||
let module_handle = v8::Local::new(scope, handle);
|
||||
let offset = scope.add_context_data(local_context, module_handle);
|
||||
assert_eq!(offset, index + 1);
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns an isolate set up for snapshotting.
|
||||
pub(crate) fn create_snapshot_creator(
|
||||
external_refs: &'static v8::ExternalReferences,
|
||||
maybe_startup_snapshot: Option<Snapshot>,
|
||||
) -> v8::OwnedIsolate {
|
||||
if let Some(snapshot) = maybe_startup_snapshot {
|
||||
match snapshot {
|
||||
Snapshot::Static(data) => {
|
||||
v8::Isolate::snapshot_creator_from_existing_snapshot(
|
||||
data,
|
||||
Some(external_refs),
|
||||
)
|
||||
}
|
||||
Snapshot::JustCreated(data) => {
|
||||
v8::Isolate::snapshot_creator_from_existing_snapshot(
|
||||
data,
|
||||
Some(external_refs),
|
||||
)
|
||||
}
|
||||
Snapshot::Boxed(data) => {
|
||||
v8::Isolate::snapshot_creator_from_existing_snapshot(
|
||||
data,
|
||||
Some(external_refs),
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
v8::Isolate::snapshot_creator(Some(external_refs))
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -1,109 +0,0 @@
|
|||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
||||
|
||||
//! This mod provides functions to remap a `JsError` based on a source map.
|
||||
|
||||
use crate::resolve_url;
|
||||
pub use sourcemap::SourceMap;
|
||||
use std::collections::HashMap;
|
||||
use std::rc::Rc;
|
||||
use std::str;
|
||||
|
||||
pub trait SourceMapGetter {
|
||||
/// Returns the raw source map file.
|
||||
fn get_source_map(&self, file_name: &str) -> Option<Vec<u8>>;
|
||||
fn get_source_line(
|
||||
&self,
|
||||
file_name: &str,
|
||||
line_number: usize,
|
||||
) -> Option<String>;
|
||||
}
|
||||
|
||||
impl<T> SourceMapGetter for Rc<T>
|
||||
where
|
||||
T: SourceMapGetter,
|
||||
{
|
||||
fn get_source_map(&self, file_name: &str) -> Option<Vec<u8>> {
|
||||
(**self).get_source_map(file_name)
|
||||
}
|
||||
|
||||
fn get_source_line(
|
||||
&self,
|
||||
file_name: &str,
|
||||
line_number: usize,
|
||||
) -> Option<String> {
|
||||
(**self).get_source_line(file_name, line_number)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct SourceMapCache {
|
||||
maps: HashMap<String, Option<SourceMap>>,
|
||||
source_lines: HashMap<(String, i64), Option<String>>,
|
||||
}
|
||||
|
||||
pub fn apply_source_map<G: SourceMapGetter + ?Sized>(
|
||||
file_name: String,
|
||||
line_number: i64,
|
||||
column_number: i64,
|
||||
cache: &mut SourceMapCache,
|
||||
getter: &G,
|
||||
) -> (String, i64, i64) {
|
||||
// Lookup expects 0-based line and column numbers, but ours are 1-based.
|
||||
let line_number = line_number - 1;
|
||||
let column_number = column_number - 1;
|
||||
|
||||
let default_pos = (file_name.clone(), line_number, column_number);
|
||||
let maybe_source_map =
|
||||
cache.maps.entry(file_name.clone()).or_insert_with(|| {
|
||||
getter
|
||||
.get_source_map(&file_name)
|
||||
.and_then(|raw_source_map| SourceMap::from_slice(&raw_source_map).ok())
|
||||
});
|
||||
let (file_name, line_number, column_number) = match maybe_source_map {
|
||||
None => default_pos,
|
||||
Some(source_map) => {
|
||||
match source_map.lookup_token(line_number as u32, column_number as u32) {
|
||||
None => default_pos,
|
||||
Some(token) => match token.get_source() {
|
||||
None => default_pos,
|
||||
Some(source_file_name) => {
|
||||
// The `source_file_name` written by tsc in the source map is
|
||||
// sometimes only the basename of the URL, or has unwanted `<`/`>`
|
||||
// around it. Use the `file_name` we get from V8 if
|
||||
// `source_file_name` does not parse as a URL.
|
||||
let file_name = match resolve_url(source_file_name) {
|
||||
Ok(m) if m.scheme() == "blob" => file_name,
|
||||
Ok(m) => m.to_string(),
|
||||
Err(_) => file_name,
|
||||
};
|
||||
(
|
||||
file_name,
|
||||
i64::from(token.get_src_line()),
|
||||
i64::from(token.get_src_col()),
|
||||
)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
(file_name, line_number + 1, column_number + 1)
|
||||
}
|
||||
|
||||
const MAX_SOURCE_LINE_LENGTH: usize = 150;
|
||||
|
||||
pub fn get_source_line<G: SourceMapGetter + ?Sized>(
|
||||
file_name: &str,
|
||||
line_number: i64,
|
||||
cache: &mut SourceMapCache,
|
||||
getter: &G,
|
||||
) -> Option<String> {
|
||||
cache
|
||||
.source_lines
|
||||
.entry((file_name.to_string(), line_number))
|
||||
.or_insert_with(|| {
|
||||
// Source lookup expects a 0-based line number, ours are 1-based.
|
||||
let s = getter.get_source_line(file_name, (line_number - 1) as usize);
|
||||
s.filter(|s| s.len() <= MAX_SOURCE_LINE_LENGTH)
|
||||
})
|
||||
.clone()
|
||||
}
|
135
core/task.rs
135
core/task.rs
|
@ -1,135 +0,0 @@
|
|||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
||||
use core::pin::Pin;
|
||||
use core::task::Context;
|
||||
use core::task::Poll;
|
||||
use futures::Future;
|
||||
use std::marker::PhantomData;
|
||||
use tokio::runtime::Handle;
|
||||
use tokio::runtime::RuntimeFlavor;
|
||||
|
||||
/// Equivalent to [`tokio::task::JoinHandle`].
|
||||
#[repr(transparent)]
|
||||
pub struct JoinHandle<R> {
|
||||
handle: tokio::task::JoinHandle<MaskResultAsSend<R>>,
|
||||
_r: PhantomData<R>,
|
||||
}
|
||||
|
||||
impl<R> JoinHandle<R> {
|
||||
/// Equivalent to [`tokio::task::JoinHandle::abort`].
|
||||
pub fn abort(&self) {
|
||||
self.handle.abort()
|
||||
}
|
||||
}
|
||||
|
||||
impl<R> Future for JoinHandle<R> {
|
||||
type Output = Result<R, tokio::task::JoinError>;
|
||||
|
||||
fn poll(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut std::task::Context<'_>,
|
||||
) -> std::task::Poll<Self::Output> {
|
||||
// SAFETY: We are sure that handle is valid here
|
||||
unsafe {
|
||||
let me: &mut Self = Pin::into_inner_unchecked(self);
|
||||
let handle = Pin::new_unchecked(&mut me.handle);
|
||||
match handle.poll(cx) {
|
||||
Poll::Pending => Poll::Pending,
|
||||
Poll::Ready(Ok(r)) => Poll::Ready(Ok(r.into_inner())),
|
||||
Poll::Ready(Err(e)) => Poll::Ready(Err(e)),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Equivalent to [`tokio::task::spawn`], but does not require the future to be [`Send`]. Must only be
|
||||
/// used on a [`RuntimeFlavor::CurrentThread`] executor, though this is only checked when running with
|
||||
/// debug assertions.
|
||||
#[inline(always)]
|
||||
pub fn spawn<F: Future<Output = R> + 'static, R: 'static>(
|
||||
f: F,
|
||||
) -> JoinHandle<R> {
|
||||
debug_assert!(
|
||||
Handle::current().runtime_flavor() == RuntimeFlavor::CurrentThread
|
||||
);
|
||||
// SAFETY: we know this is a current-thread executor
|
||||
let future = unsafe { MaskFutureAsSend::new(f) };
|
||||
JoinHandle {
|
||||
handle: tokio::task::spawn(future),
|
||||
_r: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Equivalent to [`tokio::task::spawn_blocking`]. Currently a thin wrapper around the tokio API, but this
|
||||
/// may change in the future.
|
||||
#[inline(always)]
|
||||
pub fn spawn_blocking<
|
||||
F: (FnOnce() -> R) + Send + 'static,
|
||||
R: Send + 'static,
|
||||
>(
|
||||
f: F,
|
||||
) -> JoinHandle<R> {
|
||||
let handle = tokio::task::spawn_blocking(|| MaskResultAsSend { result: f() });
|
||||
JoinHandle {
|
||||
handle,
|
||||
_r: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(transparent)]
|
||||
#[doc(hidden)]
|
||||
pub struct MaskResultAsSend<R> {
|
||||
result: R,
|
||||
}
|
||||
|
||||
/// SAFETY: We ensure that Send bounds are only faked when tokio is running on a current-thread executor
|
||||
unsafe impl<R> Send for MaskResultAsSend<R> {}
|
||||
|
||||
impl<R> MaskResultAsSend<R> {
|
||||
#[inline(always)]
|
||||
pub fn into_inner(self) -> R {
|
||||
self.result
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(transparent)]
|
||||
pub struct MaskFutureAsSend<F> {
|
||||
future: F,
|
||||
}
|
||||
|
||||
impl<F> MaskFutureAsSend<F> {
|
||||
/// Mark a non-`Send` future as `Send`. This is a trick to be able to use
|
||||
/// `tokio::spawn()` (which requires `Send` futures) in a current thread
|
||||
/// runtime.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// You must ensure that the future is actually used on the same
|
||||
/// thread, ie. always use current thread runtime flavor from Tokio.
|
||||
#[inline(always)]
|
||||
pub unsafe fn new(future: F) -> Self {
|
||||
Self { future }
|
||||
}
|
||||
}
|
||||
|
||||
// SAFETY: we are cheating here - this struct is NOT really Send,
|
||||
// but we need to mark it Send so that we can use `spawn()` in Tokio.
|
||||
unsafe impl<F> Send for MaskFutureAsSend<F> {}
|
||||
|
||||
impl<F: Future> Future for MaskFutureAsSend<F> {
|
||||
type Output = MaskResultAsSend<F::Output>;
|
||||
|
||||
fn poll(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
) -> Poll<MaskResultAsSend<F::Output>> {
|
||||
// SAFETY: We are sure that future is valid here
|
||||
unsafe {
|
||||
let me: &mut MaskFutureAsSend<F> = Pin::into_inner_unchecked(self);
|
||||
let future = Pin::new_unchecked(&mut me.future);
|
||||
match future.poll(cx) {
|
||||
Poll::Pending => Poll::Pending,
|
||||
Poll::Ready(result) => Poll::Ready(MaskResultAsSend { result }),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,143 +0,0 @@
|
|||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
||||
|
||||
use futures::task::AtomicWaker;
|
||||
use futures::Future;
|
||||
use parking_lot::Mutex;
|
||||
use std::collections::LinkedList;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
struct TaskQueueTaskWaker {
|
||||
is_ready: AtomicBool,
|
||||
waker: AtomicWaker,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
struct TaskQueueTasks {
|
||||
is_running: bool,
|
||||
wakers: LinkedList<Arc<TaskQueueTaskWaker>>,
|
||||
}
|
||||
|
||||
/// A queue that executes tasks sequentially one after the other
|
||||
/// ensuring order and that no task runs at the same time as another.
|
||||
///
|
||||
/// Note that tokio's semaphore doesn't seem to maintain order
|
||||
/// and so we can't use that in the code that uses this or use
|
||||
/// that here.
|
||||
#[derive(Debug, Default)]
|
||||
pub struct TaskQueue {
|
||||
tasks: Mutex<TaskQueueTasks>,
|
||||
}
|
||||
|
||||
impl TaskQueue {
|
||||
/// Acquires a permit where the tasks are executed one at a time
|
||||
/// and in the order that they were acquired.
|
||||
pub async fn acquire(&self) -> TaskQueuePermit {
|
||||
let acquire = TaskQueuePermitAcquire::new(self);
|
||||
acquire.await;
|
||||
TaskQueuePermit(self)
|
||||
}
|
||||
|
||||
/// Alternate API that acquires a permit internally
|
||||
/// for the duration of the future.
|
||||
pub async fn queue<R>(&self, future: impl Future<Output = R>) -> R {
|
||||
let _permit = self.acquire().await;
|
||||
future.await
|
||||
}
|
||||
}
|
||||
|
||||
/// A permit that when dropped will allow another task to proceed.
|
||||
pub struct TaskQueuePermit<'a>(&'a TaskQueue);
|
||||
|
||||
impl<'a> Drop for TaskQueuePermit<'a> {
|
||||
fn drop(&mut self) {
|
||||
let next_item = {
|
||||
let mut tasks = self.0.tasks.lock();
|
||||
let next_item = tasks.wakers.pop_front();
|
||||
tasks.is_running = next_item.is_some();
|
||||
next_item
|
||||
};
|
||||
if let Some(next_item) = next_item {
|
||||
next_item.is_ready.store(true, Ordering::SeqCst);
|
||||
next_item.waker.wake();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct TaskQueuePermitAcquire<'a> {
|
||||
task_queue: &'a TaskQueue,
|
||||
initialized: AtomicBool,
|
||||
waker: Arc<TaskQueueTaskWaker>,
|
||||
}
|
||||
|
||||
impl<'a> TaskQueuePermitAcquire<'a> {
|
||||
pub fn new(task_queue: &'a TaskQueue) -> Self {
|
||||
Self {
|
||||
task_queue,
|
||||
initialized: Default::default(),
|
||||
waker: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Future for TaskQueuePermitAcquire<'a> {
|
||||
type Output = ();
|
||||
|
||||
fn poll(
|
||||
self: std::pin::Pin<&mut Self>,
|
||||
cx: &mut std::task::Context<'_>,
|
||||
) -> std::task::Poll<Self::Output> {
|
||||
// update with the latest waker
|
||||
self.waker.waker.register(cx.waker());
|
||||
|
||||
// ensure this is initialized
|
||||
if !self.initialized.swap(true, Ordering::SeqCst) {
|
||||
let mut tasks = self.task_queue.tasks.lock();
|
||||
if !tasks.is_running {
|
||||
tasks.is_running = true;
|
||||
return std::task::Poll::Ready(());
|
||||
}
|
||||
tasks.wakers.push_back(self.waker.clone());
|
||||
return std::task::Poll::Pending;
|
||||
}
|
||||
|
||||
// check if we're ready to run
|
||||
if self.waker.is_ready.load(Ordering::SeqCst) {
|
||||
std::task::Poll::Ready(())
|
||||
} else {
|
||||
std::task::Poll::Pending
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use parking_lot::Mutex;
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::TaskQueue;
|
||||
|
||||
#[tokio::test]
|
||||
async fn task_queue_runs_one_after_other() {
|
||||
let task_queue = TaskQueue::default();
|
||||
let mut tasks = Vec::new();
|
||||
let data = Arc::new(Mutex::new(0));
|
||||
for i in 0..100 {
|
||||
let data = data.clone();
|
||||
tasks.push(task_queue.queue(async move {
|
||||
crate::task::spawn_blocking(move || {
|
||||
let mut data = data.lock();
|
||||
if *data != i {
|
||||
panic!("Value was not equal.");
|
||||
}
|
||||
*data = i + 1;
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
}));
|
||||
}
|
||||
futures::future::join_all(tasks).await;
|
||||
}
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
# Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
||||
|
||||
[package]
|
||||
name = "deno_ops"
|
||||
version = "0.69.0"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
readme = "README.md"
|
||||
repository.workspace = true
|
||||
description = "Proc macro for writing Deno Ops"
|
||||
|
||||
[lib]
|
||||
path = "./lib.rs"
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
deno-proc-macro-rules.workspace = true
|
||||
lazy-regex.workspace = true
|
||||
once_cell.workspace = true
|
||||
pmutil = "0.5.3"
|
||||
proc-macro-crate = "1.1.3"
|
||||
proc-macro2.workspace = true
|
||||
quote.workspace = true
|
||||
regex.workspace = true
|
||||
strum.workspace = true
|
||||
strum_macros.workspace = true
|
||||
syn.workspace = true
|
||||
syn2.workspace = true
|
||||
thiserror.workspace = true
|
||||
v8.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions.workspace = true
|
||||
prettyplease = "0.1.21"
|
||||
testing_macros = "0.2.7"
|
||||
trybuild = "1.0.71"
|
|
@ -1,62 +0,0 @@
|
|||
# deno_ops
|
||||
|
||||
`proc_macro` for generating highly optimized V8 functions from Deno ops.
|
||||
|
||||
```rust
|
||||
// Declare an op.
|
||||
#[op(fast)]
|
||||
pub fn op_add(_: &mut OpState, a: i32, b: i32) -> i32 {
|
||||
a + b
|
||||
}
|
||||
|
||||
// Register with an extension.
|
||||
Extension::builder()
|
||||
.ops(vec![op_add::decl()])
|
||||
.build();
|
||||
```
|
||||
|
||||
## Performance
|
||||
|
||||
The macro can optimize away code, short circuit fast paths and generate a Fast
|
||||
API impl.
|
||||
|
||||
Cases where code is optimized away:
|
||||
|
||||
- `-> ()` skips serde_v8 and `rv.set` calls.
|
||||
- `-> Result<(), E>` skips serde_v8 and `rv.set` calls for `Ok()` branch.
|
||||
- `-> ResourceId` or `-> [int]` types will use specialized method like
|
||||
`v8::ReturnValue::set_uint32`. A fast path for SMI.
|
||||
- `-> Result<ResourceId, E>` or `-> Result<[int], E>` types will be optimized
|
||||
like above for the `Ok()` branch.
|
||||
|
||||
### Fast calls
|
||||
|
||||
The macro will infer and try to auto generate V8 fast API call trait impl for
|
||||
`sync` ops with:
|
||||
|
||||
- arguments: integers, bool, `&mut OpState`, `&[u8]`, `&mut [u8]`, `&[u32]`,
|
||||
`&mut [u32]`
|
||||
- return_type: integers, bool
|
||||
|
||||
The `#[op(fast)]` attribute should be used to enforce fast call generation at
|
||||
compile time.
|
||||
|
||||
Trait gen for `async` ops & a ZeroCopyBuf equivalent type is planned and will be
|
||||
added soon.
|
||||
|
||||
### Wasm calls
|
||||
|
||||
The `#[op(wasm)]` attribute should be used for calls expected to be called from
|
||||
Wasm. This enables the fast call generation and allows seamless `WasmMemory`
|
||||
integration for generic and fast calls.
|
||||
|
||||
```rust
|
||||
#[op(wasm)]
|
||||
pub fn op_args_get(
|
||||
offset: i32,
|
||||
buffer_offset: i32,
|
||||
memory: Option<&[u8]>, // Must be last parameter. Some(..) when entered from Wasm.
|
||||
) {
|
||||
// ...
|
||||
}
|
||||
```
|
54
ops/attrs.rs
54
ops/attrs.rs
|
@ -1,54 +0,0 @@
|
|||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
||||
use syn::parse::Parse;
|
||||
use syn::parse::ParseStream;
|
||||
use syn::Error;
|
||||
use syn::Ident;
|
||||
use syn::Result;
|
||||
use syn::Token;
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct Attributes {
|
||||
pub is_unstable: bool,
|
||||
pub is_v8: bool,
|
||||
pub must_be_fast: bool,
|
||||
pub deferred: bool,
|
||||
pub is_wasm: bool,
|
||||
pub relation: Option<Ident>,
|
||||
}
|
||||
|
||||
impl Parse for Attributes {
|
||||
fn parse(input: ParseStream) -> Result<Self> {
|
||||
let mut self_ = Self::default();
|
||||
let mut fast = false;
|
||||
while let Ok(v) = input.parse::<Ident>() {
|
||||
match v.to_string().as_str() {
|
||||
"unstable" => self_.is_unstable = true,
|
||||
"v8" => self_.is_v8 = true,
|
||||
"fast" => fast = true,
|
||||
"deferred" => self_.deferred = true,
|
||||
"wasm" => self_.is_wasm = true,
|
||||
"slow" => {
|
||||
if !fast {
|
||||
return Err(Error::new(
|
||||
input.span(),
|
||||
"relational attributes can only be used with fast attribute",
|
||||
));
|
||||
}
|
||||
input.parse::<Token![=]>()?;
|
||||
self_.relation = Some(input.parse()?);
|
||||
}
|
||||
_ => {
|
||||
return Err(Error::new(
|
||||
input.span(),
|
||||
"invalid attribute, expected one of: unstable, v8, fast, deferred, wasm",
|
||||
));
|
||||
}
|
||||
};
|
||||
let _ = input.parse::<Token![,]>();
|
||||
}
|
||||
|
||||
self_.must_be_fast = self_.is_wasm || fast;
|
||||
|
||||
Ok(self_)
|
||||
}
|
||||
}
|
35
ops/deno.rs
35
ops/deno.rs
|
@ -1,35 +0,0 @@
|
|||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
||||
#![cfg(not(test))]
|
||||
|
||||
use proc_macro2::Span;
|
||||
use proc_macro2::TokenStream;
|
||||
use proc_macro_crate::crate_name;
|
||||
use proc_macro_crate::FoundCrate;
|
||||
use quote::quote;
|
||||
use syn::Ident;
|
||||
|
||||
/// Identifier to the `deno_core` crate.
|
||||
///
|
||||
/// If macro called in deno_core, `crate` is used.
|
||||
/// If macro called outside deno_core, `deno_core` OR the renamed
|
||||
/// version from Cargo.toml is used.
|
||||
pub(crate) fn import() -> TokenStream {
|
||||
let found_crate =
|
||||
crate_name("deno_core").expect("deno_core not present in `Cargo.toml`");
|
||||
|
||||
match found_crate {
|
||||
FoundCrate::Itself => {
|
||||
// TODO(@littledivy): This won't work for `deno_core` examples
|
||||
// since `crate` does not refer to `deno_core`.
|
||||
// examples must re-export deno_core to make this work
|
||||
// until Span inspection APIs are stabilized.
|
||||
//
|
||||
// https://github.com/rust-lang/rust/issues/54725
|
||||
quote!(crate)
|
||||
}
|
||||
FoundCrate::Name(name) => {
|
||||
let ident = Ident::new(&name, Span::call_site());
|
||||
quote!(#ident)
|
||||
}
|
||||
}
|
||||
}
|
363
ops/fast_call.rs
363
ops/fast_call.rs
|
@ -1,363 +0,0 @@
|
|||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
||||
//! Code generation for V8 fast calls.
|
||||
|
||||
use pmutil::q;
|
||||
use pmutil::Quote;
|
||||
use pmutil::ToTokensExt;
|
||||
use proc_macro2::Span;
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::parse_quote;
|
||||
use syn::punctuated::Punctuated;
|
||||
use syn::token::Comma;
|
||||
use syn::Generics;
|
||||
use syn::Ident;
|
||||
use syn::ItemFn;
|
||||
|
||||
use crate::optimizer::FastValue;
|
||||
use crate::optimizer::Optimizer;
|
||||
|
||||
pub(crate) struct FastImplItems {
|
||||
pub(crate) impl_and_fn: TokenStream,
|
||||
pub(crate) decl: TokenStream,
|
||||
pub(crate) active: bool,
|
||||
}
|
||||
|
||||
pub(crate) fn generate(
|
||||
core: &TokenStream,
|
||||
optimizer: &mut Optimizer,
|
||||
item_fn: &ItemFn,
|
||||
) -> FastImplItems {
|
||||
if !optimizer.fast_compatible {
|
||||
return FastImplItems {
|
||||
impl_and_fn: TokenStream::new(),
|
||||
decl: quote! { None },
|
||||
active: false,
|
||||
};
|
||||
}
|
||||
|
||||
// TODO(@littledivy): Use `let..else` on 1.65.0
|
||||
let output_ty = match &optimizer.fast_result {
|
||||
// Assert that the optimizer did not set a return type.
|
||||
//
|
||||
// @littledivy: This *could* potentially be used to optimize resolving
|
||||
// promises but knowing the return type at compile time instead of
|
||||
// serde_v8 serialization.
|
||||
Some(_) if optimizer.is_async => &FastValue::Void,
|
||||
Some(ty) => ty,
|
||||
None if optimizer.is_async => &FastValue::Void,
|
||||
None => {
|
||||
return FastImplItems {
|
||||
impl_and_fn: TokenStream::new(),
|
||||
decl: quote! { None },
|
||||
active: false,
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// We've got 2 idents.
|
||||
//
|
||||
// - op_foo, the public op declaration contains the user function.
|
||||
// - op_foo_fast_fn, the fast call function.
|
||||
let ident = item_fn.sig.ident.clone();
|
||||
let fast_fn_ident =
|
||||
Ident::new(&format!("{ident}_fast_fn"), Span::call_site());
|
||||
|
||||
// Deal with generics.
|
||||
let generics = &item_fn.sig.generics;
|
||||
let (impl_generics, _, where_clause) = generics.split_for_impl();
|
||||
|
||||
// This goes in the FastFunction impl block.
|
||||
// let mut segments = Punctuated::new();
|
||||
// {
|
||||
// let mut arguments = PathArguments::None;
|
||||
// if let Some(ref struct_generics) = struct_generics {
|
||||
// arguments = PathArguments::AngleBracketed(parse_quote! {
|
||||
// #struct_generics
|
||||
// });
|
||||
// }
|
||||
// segments.push_value(PathSegment {
|
||||
// ident: fast_ident.clone(),
|
||||
// arguments,
|
||||
// });
|
||||
// }
|
||||
|
||||
// Original inputs.
|
||||
let mut inputs = item_fn.sig.inputs.clone();
|
||||
let mut transforms = q!({});
|
||||
let mut pre_transforms = q!({});
|
||||
|
||||
// Apply parameter transforms
|
||||
for (index, input) in inputs.iter_mut().enumerate() {
|
||||
if let Some(transform) = optimizer.transforms.get(&index) {
|
||||
let quo: Quote = transform.apply_for_fast_call(core, input);
|
||||
transforms.push_tokens(&quo);
|
||||
}
|
||||
}
|
||||
|
||||
// Collect idents to be passed into function call, we can now freely
|
||||
// modify the inputs.
|
||||
let idents = inputs
|
||||
.iter()
|
||||
.map(|input| match input {
|
||||
syn::FnArg::Typed(pat_type) => match &*pat_type.pat {
|
||||
syn::Pat::Ident(pat_ident) => pat_ident.ident.clone(),
|
||||
_ => panic!("unexpected pattern"),
|
||||
},
|
||||
_ => panic!("unexpected argument"),
|
||||
})
|
||||
.collect::<Punctuated<_, Comma>>();
|
||||
|
||||
// Retain only *pure* parameters.
|
||||
let mut fast_fn_inputs = if optimizer.has_opstate_in_parameters() {
|
||||
inputs.into_iter().skip(1).collect()
|
||||
} else {
|
||||
inputs
|
||||
};
|
||||
|
||||
let mut input_variants = optimizer
|
||||
.fast_parameters
|
||||
.iter()
|
||||
.map(q_fast_ty_variant)
|
||||
.collect::<Punctuated<_, Comma>>();
|
||||
|
||||
// Apply *hard* optimizer hints.
|
||||
if optimizer.has_fast_callback_option
|
||||
|| optimizer.has_wasm_memory
|
||||
|| optimizer.needs_opstate()
|
||||
|| optimizer.is_async
|
||||
|| optimizer.needs_fast_callback_option
|
||||
{
|
||||
let decl = parse_quote! {
|
||||
fast_api_callback_options: *mut #core::v8::fast_api::FastApiCallbackOptions
|
||||
};
|
||||
|
||||
if optimizer.has_fast_callback_option || optimizer.has_wasm_memory {
|
||||
// Replace last parameter.
|
||||
assert!(fast_fn_inputs.pop().is_some());
|
||||
fast_fn_inputs.push(decl);
|
||||
} else {
|
||||
fast_fn_inputs.push(decl);
|
||||
}
|
||||
|
||||
input_variants.push(q!({ CallbackOptions }));
|
||||
}
|
||||
|
||||
// (recv, p_id, ...)
|
||||
//
|
||||
// Optimizer has already set it in the fast parameter variant list.
|
||||
if optimizer.is_async {
|
||||
if fast_fn_inputs.is_empty() {
|
||||
fast_fn_inputs.push(parse_quote! { __promise_id: i32 });
|
||||
} else {
|
||||
fast_fn_inputs.insert(0, parse_quote! { __promise_id: i32 });
|
||||
}
|
||||
}
|
||||
|
||||
let mut output_transforms = q!({});
|
||||
|
||||
if optimizer.needs_opstate()
|
||||
|| optimizer.is_async
|
||||
|| optimizer.has_fast_callback_option
|
||||
|| optimizer.has_wasm_memory
|
||||
{
|
||||
// Dark arts 🪄 ✨
|
||||
//
|
||||
// - V8 calling convention guarantees that the callback options pointer is non-null.
|
||||
// - `data` union is always initialized as the `v8::Local<v8::Value>` variant.
|
||||
// - deno_core guarantees that `data` is a v8 External pointing to an OpCtx for the
|
||||
// isolate's lifetime.
|
||||
let prelude = q!({
|
||||
let __opts: &mut v8::fast_api::FastApiCallbackOptions =
|
||||
unsafe { &mut *fast_api_callback_options };
|
||||
});
|
||||
|
||||
pre_transforms.push_tokens(&prelude);
|
||||
}
|
||||
|
||||
if optimizer.needs_opstate() || optimizer.is_async {
|
||||
// Grab the op_state identifier, the first one. ¯\_(ツ)_/¯
|
||||
let op_state = match idents.first() {
|
||||
Some(ident) if optimizer.has_opstate_in_parameters() => ident.clone(),
|
||||
// fn op_foo() -> Result<...>
|
||||
_ => Ident::new("op_state", Span::call_site()),
|
||||
};
|
||||
|
||||
let ctx = q!({
|
||||
let __ctx = unsafe {
|
||||
&*(v8::Local::<v8::External>::cast(unsafe { __opts.data.data }).value()
|
||||
as *const _ops::OpCtx)
|
||||
};
|
||||
});
|
||||
|
||||
pre_transforms.push_tokens(&ctx);
|
||||
pre_transforms.push_tokens(&match optimizer.is_async {
|
||||
false => q!(
|
||||
Vars {
|
||||
op_state: &op_state
|
||||
},
|
||||
{
|
||||
let op_state = &mut ::std::cell::RefCell::borrow_mut(&__ctx.state);
|
||||
}
|
||||
),
|
||||
true => q!(
|
||||
Vars {
|
||||
op_state: &op_state
|
||||
},
|
||||
{
|
||||
let op_state = __ctx.state.clone();
|
||||
}
|
||||
),
|
||||
});
|
||||
|
||||
if optimizer.returns_result && !optimizer.is_async {
|
||||
// Magic fallback 🪄
|
||||
//
|
||||
// If Result<T, E> is Ok(T), return T as fast value.
|
||||
//
|
||||
// Err(E) gets put into `last_fast_op_error` slot and
|
||||
//
|
||||
// V8 calls the slow path so we can take the slot
|
||||
// value and throw.
|
||||
let default = optimizer.fast_result.as_ref().unwrap().default_value();
|
||||
let result_wrap = q!(Vars { op_state, default }, {
|
||||
match result {
|
||||
Ok(result) => result,
|
||||
Err(err) => {
|
||||
op_state.last_fast_op_error.replace(err);
|
||||
__opts.fallback = true;
|
||||
default
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
output_transforms.push_tokens(&result_wrap);
|
||||
}
|
||||
}
|
||||
|
||||
if optimizer.is_async {
|
||||
let queue_future = if optimizer.returns_result {
|
||||
q!({
|
||||
let result = _ops::queue_fast_async_op(__ctx, __promise_id, result);
|
||||
})
|
||||
} else {
|
||||
q!({
|
||||
let result =
|
||||
_ops::queue_fast_async_op(__ctx, __promise_id, async move {
|
||||
Ok(result.await)
|
||||
});
|
||||
})
|
||||
};
|
||||
|
||||
output_transforms.push_tokens(&queue_future);
|
||||
}
|
||||
|
||||
if !optimizer.returns_result {
|
||||
let default_output = q!({ result });
|
||||
output_transforms.push_tokens(&default_output);
|
||||
}
|
||||
|
||||
let output = q_fast_ty(output_ty);
|
||||
// Generate the function body.
|
||||
//
|
||||
// fn f <S> (_: Local<Object>, a: T, b: U) -> R {
|
||||
// /* Transforms */
|
||||
// let a = a.into();
|
||||
// let b = b.into();
|
||||
//
|
||||
// let r = op::call(a, b);
|
||||
//
|
||||
// /* Return transform */
|
||||
// r.into()
|
||||
// }
|
||||
let fast_fn = q!(
|
||||
Vars { core, pre_transforms, op_name_fast: &fast_fn_ident, op_name: &ident, fast_fn_inputs, generics, where_clause, idents, transforms, output_transforms, output: &output },
|
||||
{
|
||||
impl generics op_name generics where_clause {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn op_name_fast (_: core::v8::Local<core::v8::Object>, fast_fn_inputs) -> output {
|
||||
use core::v8;
|
||||
use core::_ops;
|
||||
pre_transforms
|
||||
transforms
|
||||
let result = Self::call (idents);
|
||||
output_transforms
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
let output_variant = q_fast_ty_variant(output_ty);
|
||||
let mut generics: Generics = parse_quote! { #impl_generics };
|
||||
generics.where_clause = where_clause.cloned();
|
||||
|
||||
// fast_api::FastFunction::new(&[ CType::T, CType::U ], CType::T, f::<P> as *const ::std::ffi::c_void)
|
||||
let decl = q!(
|
||||
Vars {
|
||||
core: core,
|
||||
fast_fn_ident: fast_fn_ident,
|
||||
inputs: input_variants,
|
||||
output: output_variant
|
||||
},
|
||||
{
|
||||
{
|
||||
use core::v8::fast_api::CType;
|
||||
use core::v8::fast_api::Type::*;
|
||||
Some(core::v8::fast_api::FastFunction::new(
|
||||
&[inputs],
|
||||
CType::output,
|
||||
Self::fast_fn_ident as *const ::std::ffi::c_void,
|
||||
))
|
||||
}
|
||||
}
|
||||
)
|
||||
.dump();
|
||||
|
||||
let impl_and_fn = fast_fn.dump();
|
||||
|
||||
FastImplItems {
|
||||
impl_and_fn,
|
||||
decl,
|
||||
active: true,
|
||||
}
|
||||
}
|
||||
|
||||
/// Quote fast value type.
|
||||
fn q_fast_ty(v: &FastValue) -> Quote {
|
||||
match v {
|
||||
FastValue::Void => q!({ () }),
|
||||
FastValue::Bool => q!({ bool }),
|
||||
FastValue::U32 => q!({ u32 }),
|
||||
FastValue::I32 => q!({ i32 }),
|
||||
FastValue::U64 => q!({ u64 }),
|
||||
FastValue::I64 => q!({ i64 }),
|
||||
FastValue::F32 => q!({ f32 }),
|
||||
FastValue::F64 => q!({ f64 }),
|
||||
FastValue::Pointer => q!({ *mut ::std::ffi::c_void }),
|
||||
FastValue::V8Value => q!({ v8::Local<v8::Value> }),
|
||||
FastValue::Uint8Array
|
||||
| FastValue::Uint32Array
|
||||
| FastValue::Float64Array
|
||||
| FastValue::SeqOneByteString => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Quote fast value type's variant.
|
||||
fn q_fast_ty_variant(v: &FastValue) -> Quote {
|
||||
match v {
|
||||
FastValue::Void => q!({ Void }),
|
||||
FastValue::Bool => q!({ Bool }),
|
||||
FastValue::U32 => q!({ Uint32 }),
|
||||
FastValue::I32 => q!({ Int32 }),
|
||||
FastValue::U64 => q!({ Uint64 }),
|
||||
FastValue::I64 => q!({ Int64 }),
|
||||
FastValue::F32 => q!({ Float32 }),
|
||||
FastValue::F64 => q!({ Float64 }),
|
||||
FastValue::Pointer => q!({ Pointer }),
|
||||
FastValue::V8Value => q!({ V8Value }),
|
||||
FastValue::Uint8Array => q!({ TypedArray(CType::Uint8) }),
|
||||
FastValue::Uint32Array => q!({ TypedArray(CType::Uint32) }),
|
||||
FastValue::Float64Array => q!({ TypedArray(CType::Float64) }),
|
||||
FastValue::SeqOneByteString => q!({ SeqOneByteString }),
|
||||
}
|
||||
}
|
1025
ops/lib.rs
1025
ops/lib.rs
File diff suppressed because it is too large
Load diff
|
@ -1,327 +0,0 @@
|
|||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
||||
use super::generator_state::GeneratorState;
|
||||
use super::signature::Arg;
|
||||
use super::signature::NumericArg;
|
||||
use super::signature::ParsedSignature;
|
||||
use super::signature::RetVal;
|
||||
use super::signature::Special;
|
||||
use super::V8MappingError;
|
||||
use proc_macro2::Ident;
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::format_ident;
|
||||
use quote::quote;
|
||||
use std::iter::zip;
|
||||
|
||||
#[allow(unused)]
|
||||
#[derive(Debug, Default, PartialEq, Clone)]
|
||||
pub(crate) enum V8FastCallType {
|
||||
#[default]
|
||||
Void,
|
||||
Bool,
|
||||
U32,
|
||||
I32,
|
||||
U64,
|
||||
I64,
|
||||
F32,
|
||||
F64,
|
||||
Pointer,
|
||||
V8Value,
|
||||
Uint8Array,
|
||||
Uint32Array,
|
||||
Float64Array,
|
||||
SeqOneByteString,
|
||||
CallbackOptions,
|
||||
}
|
||||
|
||||
impl V8FastCallType {
|
||||
/// Quote fast value type.
|
||||
fn quote_rust_type(&self, deno_core: &TokenStream) -> TokenStream {
|
||||
match self {
|
||||
V8FastCallType::Void => quote!(()),
|
||||
V8FastCallType::Bool => quote!(bool),
|
||||
V8FastCallType::U32 => quote!(u32),
|
||||
V8FastCallType::I32 => quote!(i32),
|
||||
V8FastCallType::U64 => quote!(u64),
|
||||
V8FastCallType::I64 => quote!(i64),
|
||||
V8FastCallType::F32 => quote!(f32),
|
||||
V8FastCallType::F64 => quote!(f64),
|
||||
V8FastCallType::Pointer => quote!(*mut ::std::ffi::c_void),
|
||||
V8FastCallType::V8Value => {
|
||||
quote!(#deno_core::v8::Local<#deno_core::v8::Value>)
|
||||
}
|
||||
V8FastCallType::CallbackOptions => {
|
||||
quote!(*mut #deno_core::v8::fast_api::FastApiCallbackOptions)
|
||||
}
|
||||
V8FastCallType::SeqOneByteString => {
|
||||
quote!(*mut #deno_core::v8::fast_api::FastApiOneByteString)
|
||||
}
|
||||
V8FastCallType::Uint8Array
|
||||
| V8FastCallType::Uint32Array
|
||||
| V8FastCallType::Float64Array => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Quote fast value type's variant.
|
||||
fn quote_ctype(&self) -> TokenStream {
|
||||
match &self {
|
||||
V8FastCallType::Void => quote!(CType::Void),
|
||||
V8FastCallType::Bool => quote!(CType::Bool),
|
||||
V8FastCallType::U32 => quote!(CType::Uint32),
|
||||
V8FastCallType::I32 => quote!(CType::Int32),
|
||||
V8FastCallType::U64 => quote!(CType::Uint64),
|
||||
V8FastCallType::I64 => quote!(CType::Int64),
|
||||
V8FastCallType::F32 => quote!(CType::Float32),
|
||||
V8FastCallType::F64 => quote!(CType::Float64),
|
||||
V8FastCallType::Pointer => quote!(CType::Pointer),
|
||||
V8FastCallType::V8Value => quote!(CType::V8Value),
|
||||
V8FastCallType::CallbackOptions => quote!(CType::CallbackOptions),
|
||||
V8FastCallType::Uint8Array => unreachable!(),
|
||||
V8FastCallType::Uint32Array => unreachable!(),
|
||||
V8FastCallType::Float64Array => unreachable!(),
|
||||
V8FastCallType::SeqOneByteString => quote!(CType::SeqOneByteString),
|
||||
}
|
||||
}
|
||||
|
||||
/// Quote fast value type's variant.
|
||||
fn quote_type(&self) -> TokenStream {
|
||||
match &self {
|
||||
V8FastCallType::Void => quote!(Type::Void),
|
||||
V8FastCallType::Bool => quote!(Type::Bool),
|
||||
V8FastCallType::U32 => quote!(Type::Uint32),
|
||||
V8FastCallType::I32 => quote!(Type::Int32),
|
||||
V8FastCallType::U64 => quote!(Type::Uint64),
|
||||
V8FastCallType::I64 => quote!(Type::Int64),
|
||||
V8FastCallType::F32 => quote!(Type::Float32),
|
||||
V8FastCallType::F64 => quote!(Type::Float64),
|
||||
V8FastCallType::Pointer => quote!(Type::Pointer),
|
||||
V8FastCallType::V8Value => quote!(Type::V8Value),
|
||||
V8FastCallType::CallbackOptions => quote!(Type::CallbackOptions),
|
||||
V8FastCallType::Uint8Array => quote!(Type::TypedArray(CType::Uint8)),
|
||||
V8FastCallType::Uint32Array => quote!(Type::TypedArray(CType::Uint32)),
|
||||
V8FastCallType::Float64Array => quote!(Type::TypedArray(CType::Float64)),
|
||||
V8FastCallType::SeqOneByteString => quote!(Type::SeqOneByteString),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generate_dispatch_fast(
|
||||
generator_state: &mut GeneratorState,
|
||||
signature: &ParsedSignature,
|
||||
) -> Result<Option<(TokenStream, TokenStream)>, V8MappingError> {
|
||||
let mut inputs = vec![];
|
||||
for arg in &signature.args {
|
||||
let Some(fv) = map_arg_to_v8_fastcall_type(arg)? else {
|
||||
return Ok(None);
|
||||
};
|
||||
inputs.push(fv);
|
||||
}
|
||||
let mut names = inputs
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, _)| format_ident!("arg{i}"))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let ret_val = match &signature.ret_val {
|
||||
RetVal::Infallible(arg) => arg,
|
||||
RetVal::Result(arg) => arg,
|
||||
};
|
||||
|
||||
let output = match map_retval_to_v8_fastcall_type(ret_val)? {
|
||||
None => return Ok(None),
|
||||
Some(rv) => rv,
|
||||
};
|
||||
|
||||
let GeneratorState {
|
||||
fast_function,
|
||||
deno_core,
|
||||
result,
|
||||
opctx,
|
||||
fast_api_callback_options,
|
||||
needs_fast_api_callback_options,
|
||||
needs_fast_opctx,
|
||||
..
|
||||
} = generator_state;
|
||||
|
||||
let handle_error = match signature.ret_val {
|
||||
RetVal::Infallible(_) => quote!(),
|
||||
RetVal::Result(_) => {
|
||||
*needs_fast_api_callback_options = true;
|
||||
*needs_fast_opctx = true;
|
||||
inputs.push(V8FastCallType::CallbackOptions);
|
||||
quote! {
|
||||
let #result = match #result {
|
||||
Ok(#result) => #result,
|
||||
Err(err) => {
|
||||
// FASTCALL FALLBACK: This is where we set the errors for the slow-call error pickup path. There
|
||||
// is no code running between this and the other FASTCALL FALLBACK comment, except some V8 code
|
||||
// required to perform the fallback process. This is why the below call is safe.
|
||||
|
||||
// The reason we need to do this is because V8 does not allow exceptions to be thrown from the
|
||||
// fast call. Instead, you are required to set the fallback flag, which indicates to V8 that it
|
||||
// should re-call the slow version of the function. Technically the slow call should perform the
|
||||
// same operation and then throw the same error (because it should be idempotent), but in our
|
||||
// case we stash the error and pick it up on the slow path before doing any work.
|
||||
|
||||
// TODO(mmastrac): We should allow an #[op] flag to re-perform slow calls without the error path when
|
||||
// the method is performance sensitive.
|
||||
|
||||
// SAFETY: We guarantee that OpCtx has no mutable references once ops are live and being called,
|
||||
// allowing us to perform this one little bit of mutable magic.
|
||||
unsafe { #opctx.unsafely_set_last_error_for_ops_only(err); }
|
||||
#fast_api_callback_options.fallback = true;
|
||||
return ::std::default::Default::default();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let input_types = inputs.iter().map(|fv| fv.quote_type()).collect::<Vec<_>>();
|
||||
let output_type = output.quote_ctype();
|
||||
|
||||
let fast_definition = quote! {
|
||||
use #deno_core::v8::fast_api::Type;
|
||||
use #deno_core::v8::fast_api::CType;
|
||||
#deno_core::v8::fast_api::FastFunction::new(
|
||||
&[ Type::V8Value, #( #input_types ),* ],
|
||||
#output_type,
|
||||
Self::#fast_function as *const ::std::ffi::c_void
|
||||
)
|
||||
};
|
||||
|
||||
let output_type = output.quote_rust_type(deno_core);
|
||||
let mut types = inputs
|
||||
.iter()
|
||||
.map(|rv| rv.quote_rust_type(deno_core))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let call_idents = names.clone();
|
||||
let call_args = zip(names.iter(), signature.args.iter())
|
||||
.map(|(name, arg)| map_v8_fastcall_arg_to_arg(deno_core, name, arg))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let with_fast_api_callback_options = if *needs_fast_api_callback_options {
|
||||
types.push(V8FastCallType::CallbackOptions.quote_rust_type(deno_core));
|
||||
names.push(fast_api_callback_options.clone());
|
||||
quote! {
|
||||
let #fast_api_callback_options = unsafe { &mut *#fast_api_callback_options };
|
||||
}
|
||||
} else {
|
||||
quote!()
|
||||
};
|
||||
let with_opctx = if *needs_fast_opctx {
|
||||
quote!(
|
||||
let #opctx = unsafe {
|
||||
&*(#deno_core::v8::Local::<v8::External>::cast(unsafe { #fast_api_callback_options.data.data }).value()
|
||||
as *const #deno_core::_ops::OpCtx)
|
||||
};
|
||||
)
|
||||
} else {
|
||||
quote!()
|
||||
};
|
||||
|
||||
let fast_fn = quote!(
|
||||
fn #fast_function(
|
||||
_: #deno_core::v8::Local<#deno_core::v8::Object>,
|
||||
#( #names: #types, )*
|
||||
) -> #output_type {
|
||||
#with_fast_api_callback_options
|
||||
#with_opctx
|
||||
#(#call_args)*
|
||||
let #result = Self::call(#(#call_idents),*);
|
||||
#handle_error
|
||||
#result
|
||||
}
|
||||
);
|
||||
|
||||
Ok(Some((fast_definition, fast_fn)))
|
||||
}
|
||||
|
||||
fn map_v8_fastcall_arg_to_arg(
|
||||
deno_core: &TokenStream,
|
||||
arg_ident: &Ident,
|
||||
arg: &Arg,
|
||||
) -> TokenStream {
|
||||
let arg_temp = format_ident!("{}_temp", arg_ident);
|
||||
match arg {
|
||||
Arg::Special(Special::RefStr) => {
|
||||
quote! {
|
||||
let mut #arg_temp: [::std::mem::MaybeUninit<u8>; 1024] = [::std::mem::MaybeUninit::uninit(); 1024];
|
||||
let #arg_ident = &#deno_core::_ops::to_str_ptr(unsafe { &mut *#arg_ident }, &mut #arg_temp);
|
||||
}
|
||||
}
|
||||
Arg::Special(Special::String) => {
|
||||
quote!(let #arg_ident = #deno_core::_ops::to_string_ptr(unsafe { &mut *#arg_ident });)
|
||||
}
|
||||
Arg::Special(Special::CowStr) => {
|
||||
quote! {
|
||||
let mut #arg_temp: [::std::mem::MaybeUninit<u8>; 1024] = [::std::mem::MaybeUninit::uninit(); 1024];
|
||||
let #arg_ident = #deno_core::_ops::to_str_ptr(unsafe { &mut *#arg_ident }, &mut #arg_temp);
|
||||
}
|
||||
}
|
||||
_ => quote!(let #arg_ident = #arg_ident as _;),
|
||||
}
|
||||
}
|
||||
|
||||
fn map_arg_to_v8_fastcall_type(
|
||||
arg: &Arg,
|
||||
) -> Result<Option<V8FastCallType>, V8MappingError> {
|
||||
let rv = match arg {
|
||||
Arg::OptionNumeric(_) | Arg::SerdeV8(_) => return Ok(None),
|
||||
Arg::Numeric(NumericArg::bool) => V8FastCallType::Bool,
|
||||
Arg::Numeric(NumericArg::u32)
|
||||
| Arg::Numeric(NumericArg::u16)
|
||||
| Arg::Numeric(NumericArg::u8) => V8FastCallType::U32,
|
||||
Arg::Numeric(NumericArg::i32)
|
||||
| Arg::Numeric(NumericArg::i16)
|
||||
| Arg::Numeric(NumericArg::i8)
|
||||
| Arg::Numeric(NumericArg::__SMI__) => V8FastCallType::I32,
|
||||
Arg::Numeric(NumericArg::u64) | Arg::Numeric(NumericArg::usize) => {
|
||||
V8FastCallType::U64
|
||||
}
|
||||
Arg::Numeric(NumericArg::i64) | Arg::Numeric(NumericArg::isize) => {
|
||||
V8FastCallType::I64
|
||||
}
|
||||
// Ref strings that are one byte internally may be passed as a SeqOneByteString,
|
||||
// which gives us a FastApiOneByteString.
|
||||
Arg::Special(Special::RefStr) => V8FastCallType::SeqOneByteString,
|
||||
// Owned strings can be fast, but we'll have to copy them.
|
||||
Arg::Special(Special::String) => V8FastCallType::SeqOneByteString,
|
||||
// Cow strings can be fast, but may require copying
|
||||
Arg::Special(Special::CowStr) => V8FastCallType::SeqOneByteString,
|
||||
_ => return Err(V8MappingError::NoMapping("a fast argument", arg.clone())),
|
||||
};
|
||||
Ok(Some(rv))
|
||||
}
|
||||
|
||||
fn map_retval_to_v8_fastcall_type(
|
||||
arg: &Arg,
|
||||
) -> Result<Option<V8FastCallType>, V8MappingError> {
|
||||
let rv = match arg {
|
||||
Arg::OptionNumeric(_) | Arg::SerdeV8(_) => return Ok(None),
|
||||
Arg::Void => V8FastCallType::Void,
|
||||
Arg::Numeric(NumericArg::bool) => V8FastCallType::Bool,
|
||||
Arg::Numeric(NumericArg::u32)
|
||||
| Arg::Numeric(NumericArg::u16)
|
||||
| Arg::Numeric(NumericArg::u8) => V8FastCallType::U32,
|
||||
Arg::Numeric(NumericArg::i32)
|
||||
| Arg::Numeric(NumericArg::i16)
|
||||
| Arg::Numeric(NumericArg::i8) => V8FastCallType::I32,
|
||||
Arg::Numeric(NumericArg::u64) | Arg::Numeric(NumericArg::usize) => {
|
||||
V8FastCallType::U64
|
||||
}
|
||||
Arg::Numeric(NumericArg::i64) | Arg::Numeric(NumericArg::isize) => {
|
||||
V8FastCallType::I64
|
||||
}
|
||||
// We don't return special return types
|
||||
Arg::Option(_) => return Ok(None),
|
||||
Arg::Special(_) => return Ok(None),
|
||||
_ => {
|
||||
return Err(V8MappingError::NoMapping(
|
||||
"a fast return value",
|
||||
arg.clone(),
|
||||
))
|
||||
}
|
||||
};
|
||||
Ok(Some(rv))
|
||||
}
|
|
@ -1,401 +0,0 @@
|
|||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
||||
use super::generator_state::GeneratorState;
|
||||
use super::signature::Arg;
|
||||
use super::signature::NumericArg;
|
||||
use super::signature::ParsedSignature;
|
||||
use super::signature::RetVal;
|
||||
use super::signature::Special;
|
||||
use super::MacroConfig;
|
||||
use super::V8MappingError;
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::format_ident;
|
||||
use quote::quote;
|
||||
|
||||
pub(crate) fn generate_dispatch_slow(
|
||||
config: &MacroConfig,
|
||||
generator_state: &mut GeneratorState,
|
||||
signature: &ParsedSignature,
|
||||
) -> Result<TokenStream, V8MappingError> {
|
||||
let mut output = TokenStream::new();
|
||||
|
||||
// Fast ops require the slow op to check op_ctx for the last error
|
||||
if config.fast && matches!(signature.ret_val, RetVal::Result(_)) {
|
||||
generator_state.needs_opctx = true;
|
||||
let throw_exception = throw_exception(generator_state)?;
|
||||
// If the fast op returned an error, we must throw it rather than doing work.
|
||||
output.extend(quote!{
|
||||
// FASTCALL FALLBACK: This is where we pick up the errors for the slow-call error pickup
|
||||
// path. There is no code running between this and the other FASTCALL FALLBACK comment,
|
||||
// except some V8 code required to perform the fallback process. This is why the below call is safe.
|
||||
|
||||
// SAFETY: We guarantee that OpCtx has no mutable references once ops are live and being called,
|
||||
// allowing us to perform this one little bit of mutable magic.
|
||||
if let Some(err) = unsafe { opctx.unsafely_take_last_error_for_ops_only() } {
|
||||
#throw_exception
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
for (index, arg) in signature.args.iter().enumerate() {
|
||||
output.extend(extract_arg(generator_state, index)?);
|
||||
output.extend(from_arg(generator_state, index, arg)?);
|
||||
}
|
||||
output.extend(call(generator_state)?);
|
||||
output.extend(return_value(generator_state, &signature.ret_val)?);
|
||||
|
||||
let with_scope = if generator_state.needs_scope {
|
||||
with_scope(generator_state)
|
||||
} else {
|
||||
quote!()
|
||||
};
|
||||
|
||||
let with_opctx = if generator_state.needs_opctx {
|
||||
with_opctx(generator_state)
|
||||
} else {
|
||||
quote!()
|
||||
};
|
||||
|
||||
let with_retval = if generator_state.needs_retval {
|
||||
with_retval(generator_state)
|
||||
} else {
|
||||
quote!()
|
||||
};
|
||||
|
||||
let with_args = if generator_state.needs_args {
|
||||
with_fn_args(generator_state)
|
||||
} else {
|
||||
quote!()
|
||||
};
|
||||
|
||||
let GeneratorState {
|
||||
deno_core,
|
||||
info,
|
||||
slow_function,
|
||||
..
|
||||
} = &generator_state;
|
||||
|
||||
Ok(quote! {
|
||||
extern "C" fn #slow_function(#info: *const #deno_core::v8::FunctionCallbackInfo) {
|
||||
#with_scope
|
||||
#with_retval
|
||||
#with_args
|
||||
#with_opctx
|
||||
|
||||
#output
|
||||
}})
|
||||
}
|
||||
|
||||
fn with_scope(generator_state: &mut GeneratorState) -> TokenStream {
|
||||
let GeneratorState {
|
||||
deno_core,
|
||||
scope,
|
||||
info,
|
||||
..
|
||||
} = &generator_state;
|
||||
|
||||
quote!(let #scope = &mut unsafe { #deno_core::v8::CallbackScope::new(&*#info) };)
|
||||
}
|
||||
|
||||
fn with_retval(generator_state: &mut GeneratorState) -> TokenStream {
|
||||
let GeneratorState {
|
||||
deno_core,
|
||||
retval,
|
||||
info,
|
||||
..
|
||||
} = &generator_state;
|
||||
|
||||
quote!(let mut #retval = #deno_core::v8::ReturnValue::from_function_callback_info(unsafe { &*#info });)
|
||||
}
|
||||
|
||||
fn with_fn_args(generator_state: &mut GeneratorState) -> TokenStream {
|
||||
let GeneratorState {
|
||||
deno_core,
|
||||
fn_args,
|
||||
info,
|
||||
..
|
||||
} = &generator_state;
|
||||
|
||||
quote!(let #fn_args = #deno_core::v8::FunctionCallbackArguments::from_function_callback_info(unsafe { &*#info });)
|
||||
}
|
||||
|
||||
fn with_opctx(generator_state: &mut GeneratorState) -> TokenStream {
|
||||
let GeneratorState {
|
||||
deno_core,
|
||||
opctx,
|
||||
fn_args,
|
||||
needs_args,
|
||||
..
|
||||
} = generator_state;
|
||||
|
||||
*needs_args = true;
|
||||
quote!(let #opctx = unsafe {
|
||||
&*(#deno_core::v8::Local::<#deno_core::v8::External>::cast(#fn_args.data()).value()
|
||||
as *const #deno_core::_ops::OpCtx)
|
||||
};)
|
||||
}
|
||||
|
||||
pub fn extract_arg(
|
||||
generator_state: &mut GeneratorState,
|
||||
index: usize,
|
||||
) -> Result<TokenStream, V8MappingError> {
|
||||
let GeneratorState { fn_args, .. } = &generator_state;
|
||||
let arg_ident = generator_state.args.get(index);
|
||||
|
||||
Ok(quote!(
|
||||
let #arg_ident = #fn_args.get(#index as i32);
|
||||
))
|
||||
}
|
||||
|
||||
pub fn from_arg(
|
||||
mut generator_state: &mut GeneratorState,
|
||||
index: usize,
|
||||
arg: &Arg,
|
||||
) -> Result<TokenStream, V8MappingError> {
|
||||
let GeneratorState {
|
||||
deno_core,
|
||||
args,
|
||||
scope,
|
||||
needs_scope,
|
||||
..
|
||||
} = &mut generator_state;
|
||||
let arg_ident = args.get_mut(index).expect("Argument at index was missing");
|
||||
let arg_temp = format_ident!("{}_temp", arg_ident);
|
||||
let res = match arg {
|
||||
Arg::Numeric(NumericArg::bool) => quote! {
|
||||
let #arg_ident = #arg_ident.is_true();
|
||||
},
|
||||
Arg::Numeric(NumericArg::u8)
|
||||
| Arg::Numeric(NumericArg::u16)
|
||||
| Arg::Numeric(NumericArg::u32) => {
|
||||
quote! {
|
||||
let #arg_ident = #deno_core::_ops::to_u32(&#arg_ident) as _;
|
||||
}
|
||||
}
|
||||
Arg::Numeric(NumericArg::i8)
|
||||
| Arg::Numeric(NumericArg::i16)
|
||||
| Arg::Numeric(NumericArg::i32)
|
||||
| Arg::Numeric(NumericArg::__SMI__) => {
|
||||
quote! {
|
||||
let #arg_ident = #deno_core::_ops::to_i32(&#arg_ident) as _;
|
||||
}
|
||||
}
|
||||
Arg::Numeric(NumericArg::u64) | Arg::Numeric(NumericArg::usize) => {
|
||||
quote! {
|
||||
let #arg_ident = #deno_core::_ops::to_u64(&#arg_ident) as _;
|
||||
}
|
||||
}
|
||||
Arg::Numeric(NumericArg::i64) | Arg::Numeric(NumericArg::isize) => {
|
||||
quote! {
|
||||
let #arg_ident = #deno_core::_ops::to_i64(&#arg_ident) as _;
|
||||
}
|
||||
}
|
||||
Arg::OptionNumeric(numeric) => {
|
||||
// Ends the borrow of generator_state
|
||||
let arg_ident = arg_ident.clone();
|
||||
let some = from_arg(generator_state, index, &Arg::Numeric(*numeric))?;
|
||||
quote! {
|
||||
let #arg_ident = if #arg_ident.is_null_or_undefined() {
|
||||
None
|
||||
} else {
|
||||
#some
|
||||
Some(#arg_ident)
|
||||
};
|
||||
}
|
||||
}
|
||||
Arg::Option(Special::String) => {
|
||||
*needs_scope = true;
|
||||
quote! {
|
||||
let #arg_ident = #arg_ident.to_rust_string_lossy(#scope);
|
||||
}
|
||||
}
|
||||
Arg::Special(Special::String) => {
|
||||
*needs_scope = true;
|
||||
quote! {
|
||||
let #arg_ident = #arg_ident.to_rust_string_lossy(#scope);
|
||||
}
|
||||
}
|
||||
Arg::Special(Special::RefStr) => {
|
||||
*needs_scope = true;
|
||||
quote! {
|
||||
// Trade 1024 bytes of stack space for potentially non-allocating strings
|
||||
let mut #arg_temp: [::std::mem::MaybeUninit<u8>; 1024] = [::std::mem::MaybeUninit::uninit(); 1024];
|
||||
let #arg_ident = &#deno_core::_ops::to_str(#scope, &#arg_ident, &mut #arg_temp);
|
||||
}
|
||||
}
|
||||
Arg::Special(Special::CowStr) => {
|
||||
*needs_scope = true;
|
||||
quote! {
|
||||
// Trade 1024 bytes of stack space for potentially non-allocating strings
|
||||
let mut #arg_temp: [::std::mem::MaybeUninit<u8>; 1024] = [::std::mem::MaybeUninit::uninit(); 1024];
|
||||
let #arg_ident = #deno_core::_ops::to_str(#scope, &#arg_ident, &mut #arg_temp);
|
||||
}
|
||||
}
|
||||
_ => return Err(V8MappingError::NoMapping("a slow argument", arg.clone())),
|
||||
};
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
pub fn call(
|
||||
generator_state: &mut GeneratorState,
|
||||
) -> Result<TokenStream, V8MappingError> {
|
||||
let GeneratorState { result, .. } = &generator_state;
|
||||
|
||||
let mut tokens = TokenStream::new();
|
||||
for arg in &generator_state.args {
|
||||
tokens.extend(quote!( #arg , ));
|
||||
}
|
||||
Ok(quote! {
|
||||
let #result = Self::call( #tokens );
|
||||
})
|
||||
}
|
||||
|
||||
pub fn return_value(
|
||||
generator_state: &mut GeneratorState,
|
||||
ret_type: &RetVal,
|
||||
) -> Result<TokenStream, V8MappingError> {
|
||||
match ret_type {
|
||||
RetVal::Infallible(ret_type) => {
|
||||
return_value_infallible(generator_state, ret_type)
|
||||
}
|
||||
RetVal::Result(ret_type) => return_value_result(generator_state, ret_type),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn return_value_infallible(
|
||||
generator_state: &mut GeneratorState,
|
||||
ret_type: &Arg,
|
||||
) -> Result<TokenStream, V8MappingError> {
|
||||
let GeneratorState {
|
||||
deno_core,
|
||||
scope,
|
||||
result,
|
||||
retval,
|
||||
needs_retval,
|
||||
needs_scope,
|
||||
..
|
||||
} = generator_state;
|
||||
|
||||
let res = match ret_type {
|
||||
Arg::Void => {
|
||||
quote! {/* void */}
|
||||
}
|
||||
Arg::Numeric(NumericArg::u8)
|
||||
| Arg::Numeric(NumericArg::u16)
|
||||
| Arg::Numeric(NumericArg::u32) => {
|
||||
*needs_retval = true;
|
||||
quote!(#retval.set_uint32(#result as u32);)
|
||||
}
|
||||
Arg::Numeric(NumericArg::i8)
|
||||
| Arg::Numeric(NumericArg::i16)
|
||||
| Arg::Numeric(NumericArg::i32) => {
|
||||
*needs_retval = true;
|
||||
quote!(#retval.set_int32(#result as i32);)
|
||||
}
|
||||
Arg::Special(Special::String) => {
|
||||
*needs_retval = true;
|
||||
*needs_scope = true;
|
||||
quote! {
|
||||
if #result.is_empty() {
|
||||
#retval.set_empty_string();
|
||||
} else {
|
||||
// This should not fail in normal cases
|
||||
// TODO(mmastrac): This has extra allocations that we need to get rid of, especially if the string
|
||||
// is ASCII. We could make an "external Rust String" string in V8 from these and re-use the allocation.
|
||||
let temp = #deno_core::v8::String::new(#scope, &#result).unwrap();
|
||||
#retval.set(temp.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
Arg::Option(Special::String) => {
|
||||
*needs_retval = true;
|
||||
*needs_scope = true;
|
||||
// End the generator_state borrow
|
||||
let (result, retval) = (result.clone(), retval.clone());
|
||||
let some = return_value_infallible(
|
||||
generator_state,
|
||||
&Arg::Special(Special::String),
|
||||
)?;
|
||||
quote! {
|
||||
if let Some(#result) = #result {
|
||||
#some
|
||||
} else {
|
||||
#retval.set_null();
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
return Err(V8MappingError::NoMapping(
|
||||
"a slow return value",
|
||||
ret_type.clone(),
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
fn return_value_result(
|
||||
generator_state: &mut GeneratorState,
|
||||
ret_type: &Arg,
|
||||
) -> Result<TokenStream, V8MappingError> {
|
||||
let infallible = return_value_infallible(generator_state, ret_type)?;
|
||||
let exception = throw_exception(generator_state)?;
|
||||
|
||||
let GeneratorState { result, .. } = &generator_state;
|
||||
|
||||
let tokens = quote!(
|
||||
match #result {
|
||||
Ok(#result) => {
|
||||
#infallible
|
||||
}
|
||||
Err(err) => {
|
||||
#exception
|
||||
}
|
||||
};
|
||||
);
|
||||
Ok(tokens)
|
||||
}
|
||||
|
||||
/// Generates code to throw an exception, adding required additional dependencies as needed.
|
||||
fn throw_exception(
|
||||
generator_state: &mut GeneratorState,
|
||||
) -> Result<TokenStream, V8MappingError> {
|
||||
let maybe_scope = if generator_state.needs_scope {
|
||||
quote!()
|
||||
} else {
|
||||
with_scope(generator_state)
|
||||
};
|
||||
|
||||
let maybe_opctx = if generator_state.needs_opctx {
|
||||
quote!()
|
||||
} else {
|
||||
with_opctx(generator_state)
|
||||
};
|
||||
|
||||
let maybe_args = if generator_state.needs_args {
|
||||
quote!()
|
||||
} else {
|
||||
with_fn_args(generator_state)
|
||||
};
|
||||
|
||||
let GeneratorState {
|
||||
deno_core,
|
||||
scope,
|
||||
opctx,
|
||||
..
|
||||
} = &generator_state;
|
||||
|
||||
Ok(quote! {
|
||||
#maybe_scope
|
||||
#maybe_args
|
||||
#maybe_opctx
|
||||
let opstate = ::std::cell::RefCell::borrow(&*#opctx.state);
|
||||
let exception = #deno_core::error::to_v8_error(
|
||||
#scope,
|
||||
opstate.get_error_class_fn,
|
||||
&err,
|
||||
);
|
||||
scope.throw_exception(exception);
|
||||
return;
|
||||
})
|
||||
}
|
|
@ -1,40 +0,0 @@
|
|||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
||||
use proc_macro2::Ident;
|
||||
use proc_macro2::TokenStream;
|
||||
|
||||
pub struct GeneratorState {
|
||||
/// The path to the `deno_core` crate (either `deno_core` or `crate`, the latter used if the op is `(core)`).
|
||||
pub deno_core: TokenStream,
|
||||
|
||||
/// Identifiers for each of the arguments of the original function
|
||||
pub args: Vec<Ident>,
|
||||
/// The new identifier for the original function's contents.
|
||||
pub call: Ident,
|
||||
/// The result of the `call` function
|
||||
pub result: Ident,
|
||||
|
||||
/// The `v8::CallbackScope` used if necessary for the function.
|
||||
pub scope: Ident,
|
||||
/// The `v8::FunctionCallbackInfo` used to pass args into the slow function.
|
||||
pub info: Ident,
|
||||
/// The `v8::FunctionCallbackArguments` used to pass args into the slow function.
|
||||
pub fn_args: Ident,
|
||||
/// The `OpCtx` used for various information required for some ops.
|
||||
pub opctx: Ident,
|
||||
/// The `FastApiCallbackOptions` used in fast calls for fallback returns.
|
||||
pub fast_api_callback_options: Ident,
|
||||
/// The `v8::ReturnValue` used in the slow function
|
||||
pub retval: Ident,
|
||||
/// The "slow" function (ie: the one that isn't a fastcall)
|
||||
pub slow_function: Ident,
|
||||
/// The "fast" function (ie: a fastcall)
|
||||
pub fast_function: Ident,
|
||||
|
||||
pub needs_args: bool,
|
||||
pub needs_retval: bool,
|
||||
pub needs_scope: bool,
|
||||
pub needs_opstate: bool,
|
||||
pub needs_opctx: bool,
|
||||
pub needs_fast_opctx: bool,
|
||||
pub needs_fast_api_callback_options: bool,
|
||||
}
|
326
ops/op2/mod.rs
326
ops/op2/mod.rs
|
@ -1,326 +0,0 @@
|
|||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
||||
use deno_proc_macro_rules::rules;
|
||||
use proc_macro2::Ident;
|
||||
use proc_macro2::Span;
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::format_ident;
|
||||
use quote::quote;
|
||||
use quote::ToTokens;
|
||||
use std::iter::zip;
|
||||
use syn2::parse2;
|
||||
use syn2::parse_str;
|
||||
use syn2::FnArg;
|
||||
use syn2::ItemFn;
|
||||
use syn2::Path;
|
||||
use thiserror::Error;
|
||||
|
||||
use self::dispatch_fast::generate_dispatch_fast;
|
||||
use self::dispatch_slow::generate_dispatch_slow;
|
||||
use self::generator_state::GeneratorState;
|
||||
use self::signature::parse_signature;
|
||||
use self::signature::Arg;
|
||||
use self::signature::SignatureError;
|
||||
|
||||
pub mod dispatch_fast;
|
||||
pub mod dispatch_slow;
|
||||
pub mod generator_state;
|
||||
pub mod signature;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum Op2Error {
|
||||
#[error("Failed to match a pattern for '{0}': (input was '{1}')")]
|
||||
PatternMatchFailed(&'static str, String),
|
||||
#[error("Invalid attribute: '{0}'")]
|
||||
InvalidAttribute(String),
|
||||
#[error("Failed to parse syntax tree")]
|
||||
ParseError(#[from] syn2::Error),
|
||||
#[error("Failed to map a parsed signature to a V8 call")]
|
||||
V8MappingError(#[from] V8MappingError),
|
||||
#[error("Failed to parse signature")]
|
||||
SignatureError(#[from] SignatureError),
|
||||
#[error("This op is fast-compatible and should be marked as (fast)")]
|
||||
ShouldBeFast,
|
||||
#[error("This op is not fast-compatible and should not be marked as (fast)")]
|
||||
ShouldNotBeFast,
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum V8MappingError {
|
||||
#[error("Unable to map {1:?} to {0}")]
|
||||
NoMapping(&'static str, Arg),
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub(crate) struct MacroConfig {
|
||||
pub core: bool,
|
||||
pub fast: bool,
|
||||
}
|
||||
|
||||
impl MacroConfig {
|
||||
pub fn from_flags(flags: Vec<Ident>) -> Result<Self, Op2Error> {
|
||||
let mut config: MacroConfig = Self::default();
|
||||
for flag in flags {
|
||||
if flag == "core" {
|
||||
config.core = true;
|
||||
} else if flag == "fast" {
|
||||
config.fast = true;
|
||||
} else {
|
||||
return Err(Op2Error::InvalidAttribute(flag.to_string()));
|
||||
}
|
||||
}
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
pub fn from_tokens(tokens: TokenStream) -> Result<Self, Op2Error> {
|
||||
let attr_string = tokens.to_string();
|
||||
let config = std::panic::catch_unwind(|| {
|
||||
rules!(tokens => {
|
||||
() => {
|
||||
Ok(MacroConfig::default())
|
||||
}
|
||||
($($flags:ident),+) => {
|
||||
Self::from_flags(flags)
|
||||
}
|
||||
})
|
||||
})
|
||||
.map_err(|_| Op2Error::PatternMatchFailed("attribute", attr_string))??;
|
||||
Ok(config)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn op2(
|
||||
attr: TokenStream,
|
||||
item: TokenStream,
|
||||
) -> Result<TokenStream, Op2Error> {
|
||||
let func = parse2::<ItemFn>(item)?;
|
||||
let config = MacroConfig::from_tokens(attr)?;
|
||||
generate_op2(config, func)
|
||||
}
|
||||
|
||||
fn generate_op2(
|
||||
config: MacroConfig,
|
||||
func: ItemFn,
|
||||
) -> Result<TokenStream, Op2Error> {
|
||||
// Create a copy of the original function, named "call"
|
||||
let call = Ident::new("call", Span::call_site());
|
||||
let mut op_fn = func.clone();
|
||||
op_fn.attrs.clear();
|
||||
op_fn.sig.generics.params.clear();
|
||||
op_fn.sig.ident = call.clone();
|
||||
|
||||
// Clear inert attributes
|
||||
// TODO(mmastrac): This should limit itself to clearing ours only
|
||||
for arg in op_fn.sig.inputs.iter_mut() {
|
||||
match arg {
|
||||
FnArg::Receiver(slf) => slf.attrs.clear(),
|
||||
FnArg::Typed(ty) => ty.attrs.clear(),
|
||||
}
|
||||
}
|
||||
|
||||
let signature = parse_signature(func.attrs, func.sig.clone())?;
|
||||
let processed_args =
|
||||
zip(signature.args.iter(), &func.sig.inputs).collect::<Vec<_>>();
|
||||
|
||||
let mut args = vec![];
|
||||
let mut needs_args = false;
|
||||
for (index, _) in processed_args.iter().enumerate() {
|
||||
let input = format_ident!("arg{index}");
|
||||
args.push(input);
|
||||
needs_args = true;
|
||||
}
|
||||
|
||||
let retval = Ident::new("rv", Span::call_site());
|
||||
let result = Ident::new("result", Span::call_site());
|
||||
let fn_args = Ident::new("args", Span::call_site());
|
||||
let scope = Ident::new("scope", Span::call_site());
|
||||
let info = Ident::new("info", Span::call_site());
|
||||
let opctx = Ident::new("opctx", Span::call_site());
|
||||
let slow_function = Ident::new("v8_fn_ptr", Span::call_site());
|
||||
let fast_function = Ident::new("v8_fn_ptr_fast", Span::call_site());
|
||||
let fast_api_callback_options =
|
||||
Ident::new("fast_api_callback_options", Span::call_site());
|
||||
|
||||
let deno_core = if config.core {
|
||||
syn2::parse_str::<Path>("crate")
|
||||
} else {
|
||||
syn2::parse_str::<Path>("deno_core")
|
||||
}
|
||||
.expect("Parsing crate should not fail")
|
||||
.into_token_stream();
|
||||
|
||||
let mut generator_state = GeneratorState {
|
||||
args,
|
||||
fn_args,
|
||||
call,
|
||||
scope,
|
||||
info,
|
||||
opctx,
|
||||
fast_api_callback_options,
|
||||
deno_core,
|
||||
result,
|
||||
retval,
|
||||
needs_args,
|
||||
slow_function,
|
||||
fast_function,
|
||||
needs_retval: false,
|
||||
needs_scope: false,
|
||||
needs_opctx: false,
|
||||
needs_opstate: false,
|
||||
needs_fast_opctx: false,
|
||||
needs_fast_api_callback_options: false,
|
||||
};
|
||||
|
||||
let name = func.sig.ident;
|
||||
|
||||
let slow_fn =
|
||||
generate_dispatch_slow(&config, &mut generator_state, &signature)?;
|
||||
let (fast_definition, fast_fn) =
|
||||
match generate_dispatch_fast(&mut generator_state, &signature)? {
|
||||
Some((fast_definition, fast_fn)) => {
|
||||
if !config.fast {
|
||||
return Err(Op2Error::ShouldBeFast);
|
||||
}
|
||||
(quote!(Some({#fast_definition})), fast_fn)
|
||||
}
|
||||
None => {
|
||||
if config.fast {
|
||||
return Err(Op2Error::ShouldNotBeFast);
|
||||
}
|
||||
(quote!(None), quote!())
|
||||
}
|
||||
};
|
||||
|
||||
let GeneratorState {
|
||||
deno_core,
|
||||
slow_function,
|
||||
..
|
||||
} = &generator_state;
|
||||
|
||||
let arg_count: usize = generator_state.args.len();
|
||||
let vis = func.vis;
|
||||
let generic = signature
|
||||
.generic_bounds
|
||||
.keys()
|
||||
.map(|s| format_ident!("{s}"))
|
||||
.collect::<Vec<_>>();
|
||||
let bound = signature
|
||||
.generic_bounds
|
||||
.values()
|
||||
.map(|p| parse_str::<Path>(p).expect("Failed to reparse path"))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
Ok(quote! {
|
||||
#[allow(non_camel_case_types)]
|
||||
#vis struct #name <#(#generic),*> {
|
||||
// We need to mark these type parameters as used, so we use a PhantomData
|
||||
_unconstructable: ::std::marker::PhantomData<(#(#generic),*)>
|
||||
}
|
||||
|
||||
impl <#(#generic : #bound),*> #deno_core::_ops::Op for #name <#(#generic),*> {
|
||||
const NAME: &'static str = stringify!(#name);
|
||||
const DECL: #deno_core::_ops::OpDecl = #deno_core::_ops::OpDecl {
|
||||
name: stringify!(#name),
|
||||
v8_fn_ptr: Self::#slow_function as _,
|
||||
enabled: true,
|
||||
fast_fn: #fast_definition,
|
||||
is_async: false,
|
||||
is_unstable: false,
|
||||
is_v8: false,
|
||||
arg_count: #arg_count as u8,
|
||||
};
|
||||
}
|
||||
|
||||
impl <#(#generic : #bound),*> #name <#(#generic),*> {
|
||||
pub const fn name() -> &'static str {
|
||||
stringify!(#name)
|
||||
}
|
||||
|
||||
pub const fn decl() -> #deno_core::_ops::OpDecl {
|
||||
#deno_core::_ops::OpDecl {
|
||||
name: stringify!(#name),
|
||||
v8_fn_ptr: Self::#slow_function as _,
|
||||
enabled: true,
|
||||
fast_fn: #fast_definition,
|
||||
is_async: false,
|
||||
is_unstable: false,
|
||||
is_v8: false,
|
||||
arg_count: #arg_count as u8,
|
||||
}
|
||||
}
|
||||
|
||||
#fast_fn
|
||||
#slow_fn
|
||||
|
||||
#[inline(always)]
|
||||
#op_fn
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use pretty_assertions::assert_eq;
|
||||
use std::path::PathBuf;
|
||||
use syn2::parse_str;
|
||||
use syn2::File;
|
||||
use syn2::Item;
|
||||
|
||||
#[testing_macros::fixture("op2/test_cases/**/*.rs")]
|
||||
fn test_signature_parser(input: PathBuf) {
|
||||
let update_expected = std::env::var("UPDATE_EXPECTED").is_ok();
|
||||
|
||||
let source =
|
||||
std::fs::read_to_string(&input).expect("Failed to read test file");
|
||||
let file = parse_str::<File>(&source).expect("Failed to parse Rust file");
|
||||
let mut expected_out = vec![];
|
||||
for item in file.items {
|
||||
if let Item::Fn(mut func) = item {
|
||||
let mut config = None;
|
||||
func.attrs.retain(|attr| {
|
||||
let tokens = attr.into_token_stream();
|
||||
let attr_string = attr.clone().into_token_stream().to_string();
|
||||
println!("{}", attr_string);
|
||||
use syn2 as syn;
|
||||
if let Some(new_config) = rules!(tokens => {
|
||||
(#[op2]) => {
|
||||
Some(MacroConfig::default())
|
||||
}
|
||||
(#[op2( $($x:ident),* )]) => {
|
||||
Some(MacroConfig::from_flags(x).expect("Failed to parse attribute"))
|
||||
}
|
||||
(#[$_attr:meta]) => {
|
||||
None
|
||||
}
|
||||
}) {
|
||||
config = Some(new_config);
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
});
|
||||
let tokens =
|
||||
generate_op2(config.unwrap(), func).expect("Failed to generate op");
|
||||
println!("======== Raw tokens ========:\n{}", tokens.clone());
|
||||
let tree = syn::parse2(tokens).unwrap();
|
||||
let actual = prettyplease::unparse(&tree);
|
||||
println!("======== Generated ========:\n{}", actual);
|
||||
expected_out.push(actual);
|
||||
}
|
||||
}
|
||||
|
||||
let expected_out = expected_out.join("\n");
|
||||
|
||||
if update_expected {
|
||||
std::fs::write(input.with_extension("out"), expected_out)
|
||||
.expect("Failed to write expectation file");
|
||||
} else {
|
||||
let expected = std::fs::read_to_string(input.with_extension("out"))
|
||||
.expect("Failed to read expectation file");
|
||||
assert_eq!(
|
||||
expected, expected_out,
|
||||
"Failed to match expectation. Use UPDATE_EXPECTED=1."
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,741 +0,0 @@
|
|||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
||||
use deno_proc_macro_rules::rules;
|
||||
use proc_macro2::Ident;
|
||||
use proc_macro2::Span;
|
||||
use quote::quote;
|
||||
use quote::ToTokens;
|
||||
use std::collections::BTreeMap;
|
||||
use strum::IntoEnumIterator;
|
||||
use strum::IntoStaticStr;
|
||||
use strum_macros::EnumIter;
|
||||
use strum_macros::EnumString;
|
||||
use syn2::Attribute;
|
||||
use syn2::FnArg;
|
||||
use syn2::GenericParam;
|
||||
use syn2::Generics;
|
||||
use syn2::Pat;
|
||||
use syn2::ReturnType;
|
||||
use syn2::Signature;
|
||||
use syn2::Type;
|
||||
use syn2::TypePath;
|
||||
use thiserror::Error;
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
#[derive(
|
||||
Copy, Clone, Debug, Eq, PartialEq, IntoStaticStr, EnumString, EnumIter,
|
||||
)]
|
||||
pub enum NumericArg {
|
||||
/// A placeholder argument for arguments annotated with #[smi].
|
||||
__SMI__,
|
||||
/// A placeholder argument for void data.
|
||||
__VOID__,
|
||||
bool,
|
||||
i8,
|
||||
u8,
|
||||
i16,
|
||||
u16,
|
||||
i32,
|
||||
u32,
|
||||
i64,
|
||||
u64,
|
||||
f32,
|
||||
f64,
|
||||
isize,
|
||||
usize,
|
||||
}
|
||||
|
||||
impl ToTokens for NumericArg {
|
||||
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
|
||||
let ident = Ident::new(self.into(), Span::call_site());
|
||||
tokens.extend(quote! { #ident })
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Copy, Clone, Debug, Eq, PartialEq, IntoStaticStr, EnumString, EnumIter,
|
||||
)]
|
||||
pub enum V8Arg {
|
||||
External,
|
||||
Object,
|
||||
Array,
|
||||
ArrayBuffer,
|
||||
ArrayBufferView,
|
||||
DataView,
|
||||
TypedArray,
|
||||
BigInt64Array,
|
||||
BigUint64Array,
|
||||
Float32Array,
|
||||
Float64Array,
|
||||
Int16Array,
|
||||
Int32Array,
|
||||
Int8Array,
|
||||
Uint16Array,
|
||||
Uint32Array,
|
||||
Uint8Array,
|
||||
Uint8ClampedArray,
|
||||
BigIntObject,
|
||||
BooleanObject,
|
||||
Date,
|
||||
Function,
|
||||
Map,
|
||||
NumberObject,
|
||||
Promise,
|
||||
PromiseResolver,
|
||||
Proxy,
|
||||
RegExp,
|
||||
Set,
|
||||
SharedArrayBuffer,
|
||||
StringObject,
|
||||
SymbolObject,
|
||||
WasmMemoryObject,
|
||||
WasmModuleObject,
|
||||
Primitive,
|
||||
BigInt,
|
||||
Boolean,
|
||||
Name,
|
||||
String,
|
||||
Symbol,
|
||||
Number,
|
||||
Integer,
|
||||
Int32,
|
||||
Uint32,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||
pub enum Special {
|
||||
HandleScope,
|
||||
OpState,
|
||||
String,
|
||||
CowStr,
|
||||
RefStr,
|
||||
FastApiCallbackOptions,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||
pub enum RefType {
|
||||
Ref,
|
||||
Mut,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub enum Arg {
|
||||
Void,
|
||||
Special(Special),
|
||||
Ref(RefType, Special),
|
||||
RcRefCell(Special),
|
||||
Option(Special),
|
||||
OptionNumeric(NumericArg),
|
||||
Slice(RefType, NumericArg),
|
||||
Ptr(RefType, NumericArg),
|
||||
V8Local(V8Arg),
|
||||
Numeric(NumericArg),
|
||||
SerdeV8(String),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub enum RetVal {
|
||||
Infallible(Arg),
|
||||
Result(Arg),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct ParsedSignature {
|
||||
// The parsed arguments
|
||||
pub args: Vec<Arg>,
|
||||
// The argument names
|
||||
pub names: Vec<String>,
|
||||
// The parsed return value
|
||||
pub ret_val: RetVal,
|
||||
// One and only one lifetime allowed
|
||||
pub lifetime: Option<String>,
|
||||
// Generic bounds: each generic must have one and only simple trait bound
|
||||
pub generic_bounds: BTreeMap<String, String>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||
enum AttributeModifier {
|
||||
/// #[serde], for serde_v8 types.
|
||||
Serde,
|
||||
/// #[smi], for small integers
|
||||
Smi,
|
||||
/// #[string], for strings.
|
||||
String,
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum SignatureError {
|
||||
#[error("Invalid argument: '{0}'")]
|
||||
ArgError(String, #[source] ArgError),
|
||||
#[error("Invalid return type")]
|
||||
RetError(#[from] ArgError),
|
||||
#[error("Only one lifetime is permitted")]
|
||||
TooManyLifetimes,
|
||||
#[error("Generic '{0}' must have one and only bound (either <T> and 'where T: Trait', or <T: Trait>)")]
|
||||
GenericBoundCardinality(String),
|
||||
#[error("Where clause predicate '{0}' (eg: where T: Trait) must appear in generics list (eg: <T>)")]
|
||||
WherePredicateMustAppearInGenerics(String),
|
||||
#[error("All generics must appear only once in the generics parameter list or where clause")]
|
||||
DuplicateGeneric(String),
|
||||
#[error("Generic lifetime '{0}' may not have bounds (eg: <'a: 'b>)")]
|
||||
LifetimesMayNotHaveBounds(String),
|
||||
#[error("Invalid generic: '{0}' Only simple generics bounds are allowed (eg: T: Trait)")]
|
||||
InvalidGeneric(String),
|
||||
#[error("Invalid predicate: '{0}' Only simple where predicates are allowed (eg: T: Trait)")]
|
||||
InvalidWherePredicate(String),
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum ArgError {
|
||||
#[error("Invalid self argument")]
|
||||
InvalidSelf,
|
||||
#[error("Invalid argument type: {0}")]
|
||||
InvalidType(String),
|
||||
#[error(
|
||||
"Invalid argument type path (should this be #[smi] or #[serde]?): {0}"
|
||||
)]
|
||||
InvalidTypePath(String),
|
||||
#[error("Too many attributes")]
|
||||
TooManyAttributes,
|
||||
#[error("Invalid #[serde] type: {0}")]
|
||||
InvalidSerdeType(String),
|
||||
#[error("Cannot use #[serde] for type: {0}")]
|
||||
InvalidSerdeAttributeType(String),
|
||||
#[error("Invalid v8 type: {0}")]
|
||||
InvalidV8Type(String),
|
||||
#[error("Internal error: {0}")]
|
||||
InternalError(String),
|
||||
#[error("Missing a #[string] attribute")]
|
||||
MissingStringAttribute,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Default)]
|
||||
struct Attributes {
|
||||
primary: Option<AttributeModifier>,
|
||||
}
|
||||
|
||||
fn stringify_token(tokens: impl ToTokens) -> String {
|
||||
tokens
|
||||
.into_token_stream()
|
||||
.into_iter()
|
||||
.map(|s| s.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join("")
|
||||
}
|
||||
|
||||
pub fn parse_signature(
|
||||
attributes: Vec<Attribute>,
|
||||
signature: Signature,
|
||||
) -> Result<ParsedSignature, SignatureError> {
|
||||
let mut args = vec![];
|
||||
let mut names = vec![];
|
||||
for input in signature.inputs {
|
||||
let name = match &input {
|
||||
FnArg::Receiver(_) => "self".to_owned(),
|
||||
FnArg::Typed(ty) => match &*ty.pat {
|
||||
Pat::Ident(ident) => ident.ident.to_string(),
|
||||
_ => "(complex)".to_owned(),
|
||||
},
|
||||
};
|
||||
names.push(name.clone());
|
||||
args.push(
|
||||
parse_arg(input).map_err(|err| SignatureError::ArgError(name, err))?,
|
||||
);
|
||||
}
|
||||
let ret_val =
|
||||
parse_return(parse_attributes(&attributes)?, &signature.output)?;
|
||||
let lifetime = parse_lifetime(&signature.generics)?;
|
||||
let generic_bounds = parse_generics(&signature.generics)?;
|
||||
Ok(ParsedSignature {
|
||||
args,
|
||||
names,
|
||||
ret_val,
|
||||
lifetime,
|
||||
generic_bounds,
|
||||
})
|
||||
}
|
||||
|
||||
/// Extract one lifetime from the [`syn2::Generics`], ensuring that the lifetime is valid
|
||||
/// and has no bounds.
|
||||
fn parse_lifetime(
|
||||
generics: &Generics,
|
||||
) -> Result<Option<String>, SignatureError> {
|
||||
let mut res = None;
|
||||
for param in &generics.params {
|
||||
if let GenericParam::Lifetime(lt) = param {
|
||||
if !lt.bounds.is_empty() {
|
||||
return Err(SignatureError::LifetimesMayNotHaveBounds(
|
||||
lt.lifetime.to_string(),
|
||||
));
|
||||
}
|
||||
if res.is_some() {
|
||||
return Err(SignatureError::TooManyLifetimes);
|
||||
}
|
||||
res = Some(lt.lifetime.ident.to_string());
|
||||
}
|
||||
}
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
/// Parse and validate generics. We require one and only one trait bound for each generic
|
||||
/// parameter. Tries to sanity check and return reasonable errors for possible signature errors.
|
||||
fn parse_generics(
|
||||
generics: &Generics,
|
||||
) -> Result<BTreeMap<String, String>, SignatureError> {
|
||||
let mut where_clauses = BTreeMap::new();
|
||||
|
||||
// First, extract the where clause so we can detect duplicated predicates
|
||||
if let Some(where_clause) = &generics.where_clause {
|
||||
for predicate in &where_clause.predicates {
|
||||
let predicate = predicate.to_token_stream();
|
||||
let (generic_name, bound) = std::panic::catch_unwind(|| {
|
||||
use syn2 as syn;
|
||||
rules!(predicate => {
|
||||
($t:ident : $bound:path) => (t.to_string(), stringify_token(bound)),
|
||||
})
|
||||
})
|
||||
.map_err(|_| {
|
||||
SignatureError::InvalidWherePredicate(predicate.to_string())
|
||||
})?;
|
||||
if where_clauses.insert(generic_name.clone(), bound).is_some() {
|
||||
return Err(SignatureError::DuplicateGeneric(generic_name));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut res = BTreeMap::new();
|
||||
for param in &generics.params {
|
||||
if let GenericParam::Type(ty) = param {
|
||||
let ty = ty.to_token_stream();
|
||||
let (name, bound) = std::panic::catch_unwind(|| {
|
||||
use syn2 as syn;
|
||||
rules!(ty => {
|
||||
($t:ident : $bound:path) => (t.to_string(), Some(stringify_token(bound))),
|
||||
($t:ident) => (t.to_string(), None),
|
||||
})
|
||||
}).map_err(|_| SignatureError::InvalidGeneric(ty.to_string()))?;
|
||||
let bound = match bound {
|
||||
Some(bound) => {
|
||||
if where_clauses.contains_key(&name) {
|
||||
return Err(SignatureError::GenericBoundCardinality(name));
|
||||
}
|
||||
bound
|
||||
}
|
||||
None => {
|
||||
let Some(bound) = where_clauses.remove(&name) else {
|
||||
return Err(SignatureError::GenericBoundCardinality(name));
|
||||
};
|
||||
bound
|
||||
}
|
||||
};
|
||||
if res.contains_key(&name) {
|
||||
return Err(SignatureError::DuplicateGeneric(name));
|
||||
}
|
||||
res.insert(name, bound);
|
||||
}
|
||||
}
|
||||
if !where_clauses.is_empty() {
|
||||
return Err(SignatureError::WherePredicateMustAppearInGenerics(
|
||||
where_clauses.into_keys().next().unwrap(),
|
||||
));
|
||||
}
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
fn parse_attributes(attributes: &[Attribute]) -> Result<Attributes, ArgError> {
|
||||
let attrs = attributes
|
||||
.iter()
|
||||
.filter_map(parse_attribute)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if attrs.is_empty() {
|
||||
return Ok(Attributes::default());
|
||||
}
|
||||
if attrs.len() > 1 {
|
||||
return Err(ArgError::TooManyAttributes);
|
||||
}
|
||||
Ok(Attributes {
|
||||
primary: Some(*attrs.get(0).unwrap()),
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_attribute(attr: &Attribute) -> Option<AttributeModifier> {
|
||||
let tokens = attr.into_token_stream();
|
||||
use syn2 as syn;
|
||||
std::panic::catch_unwind(|| {
|
||||
rules!(tokens => {
|
||||
(#[serde]) => Some(AttributeModifier::Serde),
|
||||
(#[smi]) => Some(AttributeModifier::Smi),
|
||||
(#[string]) => Some(AttributeModifier::String),
|
||||
(#[$_attr:meta]) => None,
|
||||
})
|
||||
})
|
||||
.expect("Failed to parse an attribute")
|
||||
}
|
||||
|
||||
fn parse_return(
|
||||
attrs: Attributes,
|
||||
rt: &ReturnType,
|
||||
) -> Result<RetVal, ArgError> {
|
||||
match rt {
|
||||
ReturnType::Default => Ok(RetVal::Infallible(Arg::Void)),
|
||||
ReturnType::Type(_, ty) => {
|
||||
let s = stringify_token(ty);
|
||||
let tokens = ty.into_token_stream();
|
||||
use syn2 as syn;
|
||||
|
||||
std::panic::catch_unwind(|| {
|
||||
rules!(tokens => {
|
||||
// x::y::Result<Value>, like io::Result and other specialty result types
|
||||
($($_package:ident ::)* Result < $ty:ty >) => {
|
||||
Ok(RetVal::Result(parse_type(attrs, &ty)?))
|
||||
}
|
||||
// x::y::Result<Value, Error>
|
||||
($($_package:ident ::)* Result < $ty:ty, $_error:ty >) => {
|
||||
Ok(RetVal::Result(parse_type(attrs, &ty)?))
|
||||
}
|
||||
($ty:ty) => {
|
||||
Ok(RetVal::Infallible(parse_type(attrs, &ty)?))
|
||||
}
|
||||
})
|
||||
})
|
||||
.map_err(|e| {
|
||||
ArgError::InternalError(format!(
|
||||
"parse_return({}) {}",
|
||||
s,
|
||||
e.downcast::<&str>().unwrap_or_default()
|
||||
))
|
||||
})?
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_type_path(attrs: Attributes, tp: &TypePath) -> Result<Arg, ArgError> {
|
||||
if tp.path.segments.len() == 1 {
|
||||
let segment = tp.path.segments.first().unwrap().ident.to_string();
|
||||
for numeric in NumericArg::iter() {
|
||||
if Into::<&'static str>::into(numeric) == segment.as_str() {
|
||||
return Ok(Arg::Numeric(numeric));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
use syn2 as syn;
|
||||
|
||||
let tokens = tp.clone().into_token_stream();
|
||||
std::panic::catch_unwind(|| {
|
||||
rules!(tokens => {
|
||||
( $( std :: str :: )? String ) => {
|
||||
if attrs.primary == Some(AttributeModifier::String) {
|
||||
Ok(Arg::Special(Special::String))
|
||||
} else {
|
||||
Err(ArgError::MissingStringAttribute)
|
||||
}
|
||||
}
|
||||
( $( std :: str :: )? str ) => {
|
||||
// We should not hit this path with a #[string] argument
|
||||
Err(ArgError::MissingStringAttribute)
|
||||
}
|
||||
( $( std :: borrow :: )? Cow < str > ) => {
|
||||
if attrs.primary == Some(AttributeModifier::String) {
|
||||
Ok(Arg::Special(Special::CowStr))
|
||||
} else {
|
||||
Err(ArgError::MissingStringAttribute)
|
||||
}
|
||||
}
|
||||
( $( std :: ffi :: )? c_void ) => Ok(Arg::Numeric(NumericArg::__VOID__)),
|
||||
( OpState ) => Ok(Arg::Special(Special::OpState)),
|
||||
( v8 :: HandleScope ) => Ok(Arg::Special(Special::HandleScope)),
|
||||
( v8 :: FastApiCallbackOptions ) => Ok(Arg::Special(Special::FastApiCallbackOptions)),
|
||||
( v8 :: Local < $( $_scope:lifetime , )? v8 :: $v8:ident >) => Ok(Arg::V8Local(parse_v8_type(&v8)?)),
|
||||
( Rc < RefCell < $ty:ty > > ) => Ok(Arg::RcRefCell(parse_type_special(attrs, &ty)?)),
|
||||
( Option < $ty:ty > ) => {
|
||||
match parse_type(attrs, &ty)? {
|
||||
Arg::Special(special) => Ok(Arg::Option(special)),
|
||||
Arg::Numeric(numeric) => Ok(Arg::OptionNumeric(numeric)),
|
||||
_ => Err(ArgError::InvalidType(stringify_token(ty)))
|
||||
}
|
||||
}
|
||||
( $any:ty ) => Err(ArgError::InvalidTypePath(stringify_token(any))),
|
||||
})
|
||||
}).map_err(|e| ArgError::InternalError(format!("parse_type_path {e:?}")))?
|
||||
}
|
||||
|
||||
fn parse_v8_type(v8: &Ident) -> Result<V8Arg, ArgError> {
|
||||
let v8 = v8.to_string();
|
||||
V8Arg::try_from(v8.as_str()).map_err(|_| ArgError::InvalidV8Type(v8))
|
||||
}
|
||||
|
||||
fn parse_type_special(
|
||||
attrs: Attributes,
|
||||
ty: &Type,
|
||||
) -> Result<Special, ArgError> {
|
||||
match parse_type(attrs, ty)? {
|
||||
Arg::Special(special) => Ok(special),
|
||||
_ => Err(ArgError::InvalidType(stringify_token(ty))),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_type(attrs: Attributes, ty: &Type) -> Result<Arg, ArgError> {
|
||||
if let Some(primary) = attrs.primary {
|
||||
match primary {
|
||||
AttributeModifier::Serde => match ty {
|
||||
Type::Path(of) => {
|
||||
// If this type will parse without #[serde], it is illegal to use this type with #[serde]
|
||||
if parse_type_path(Attributes::default(), of).is_ok() {
|
||||
return Err(ArgError::InvalidSerdeAttributeType(stringify_token(
|
||||
ty,
|
||||
)));
|
||||
}
|
||||
return Ok(Arg::SerdeV8(stringify_token(of.path.clone())));
|
||||
}
|
||||
_ => return Err(ArgError::InvalidSerdeType(stringify_token(ty))),
|
||||
},
|
||||
AttributeModifier::String => match ty {
|
||||
Type::Path(of) => {
|
||||
return parse_type_path(attrs, of);
|
||||
}
|
||||
Type::Reference(of) => {
|
||||
let mut_type = if of.mutability.is_some() {
|
||||
RefType::Mut
|
||||
} else {
|
||||
RefType::Ref
|
||||
};
|
||||
let tokens = of.elem.clone().into_token_stream();
|
||||
use syn2 as syn;
|
||||
return rules!(tokens => {
|
||||
(str) => Ok(Arg::Special(Special::RefStr)),
|
||||
($_ty:ty) => Ok(Arg::Ref(mut_type, parse_type_special(attrs, &of.elem)?)),
|
||||
});
|
||||
}
|
||||
_ => return Err(ArgError::InvalidSerdeType(stringify_token(ty))),
|
||||
},
|
||||
AttributeModifier::Smi => {
|
||||
return Ok(Arg::Numeric(NumericArg::__SMI__));
|
||||
}
|
||||
}
|
||||
};
|
||||
match ty {
|
||||
Type::Tuple(of) => {
|
||||
if of.elems.is_empty() {
|
||||
Ok(Arg::Void)
|
||||
} else {
|
||||
Err(ArgError::InvalidType(stringify_token(ty)))
|
||||
}
|
||||
}
|
||||
Type::Reference(of) => {
|
||||
let mut_type = if of.mutability.is_some() {
|
||||
RefType::Mut
|
||||
} else {
|
||||
RefType::Ref
|
||||
};
|
||||
match &*of.elem {
|
||||
Type::Slice(of) => match parse_type(attrs, &of.elem)? {
|
||||
Arg::Numeric(numeric) => Ok(Arg::Slice(mut_type, numeric)),
|
||||
_ => Err(ArgError::InvalidType(stringify_token(ty))),
|
||||
},
|
||||
Type::Path(of) => match parse_type_path(attrs, of)? {
|
||||
Arg::Special(special) => Ok(Arg::Ref(mut_type, special)),
|
||||
_ => Err(ArgError::InvalidType(stringify_token(ty))),
|
||||
},
|
||||
_ => Err(ArgError::InvalidType(stringify_token(ty))),
|
||||
}
|
||||
}
|
||||
Type::Ptr(of) => {
|
||||
let mut_type = if of.mutability.is_some() {
|
||||
RefType::Mut
|
||||
} else {
|
||||
RefType::Ref
|
||||
};
|
||||
match &*of.elem {
|
||||
Type::Path(of) => match parse_type_path(attrs, of)? {
|
||||
Arg::Numeric(numeric) => Ok(Arg::Ptr(mut_type, numeric)),
|
||||
_ => Err(ArgError::InvalidType(stringify_token(ty))),
|
||||
},
|
||||
_ => Err(ArgError::InvalidType(stringify_token(ty))),
|
||||
}
|
||||
}
|
||||
Type::Path(of) => parse_type_path(attrs, of),
|
||||
_ => Err(ArgError::InvalidType(stringify_token(ty))),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_arg(arg: FnArg) -> Result<Arg, ArgError> {
|
||||
let FnArg::Typed(typed) = arg else {
|
||||
return Err(ArgError::InvalidSelf);
|
||||
};
|
||||
parse_type(parse_attributes(&typed.attrs)?, &typed.ty)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::op2::signature::parse_signature;
|
||||
use syn2::parse_str;
|
||||
use syn2::ItemFn;
|
||||
|
||||
// We can't test pattern args :/
|
||||
// https://github.com/rust-lang/rfcs/issues/2688
|
||||
macro_rules! test {
|
||||
(
|
||||
// Function attributes
|
||||
$(# [ $fn_attr:ident ])?
|
||||
// fn name < 'scope, GENERIC1, GENERIC2, ... >
|
||||
fn $name:ident $( < $scope:lifetime $( , $generic:ident)* >)?
|
||||
(
|
||||
// Argument attribute, argument
|
||||
$( $(# [ $attr:ident ])? $ident:ident : $ty:ty ),*
|
||||
)
|
||||
// Return value
|
||||
$(-> $(# [ $ret_attr:ident ])? $ret:ty)?
|
||||
// Where clause
|
||||
$( where $($trait:ident : $bounds:path),* )?
|
||||
;
|
||||
// Expected return value
|
||||
$( < $( $lifetime_res:lifetime )? $(, $generic_res:ident : $bounds_res:path )* >)? ( $( $arg_res:expr ),* ) -> $ret_res:expr ) => {
|
||||
#[test]
|
||||
fn $name() {
|
||||
test(
|
||||
stringify!($( #[$fn_attr] )? fn op $( < $scope $( , $generic)* >)? ( $( $( #[$attr] )? $ident : $ty ),* ) $(-> $( #[$ret_attr] )? $ret)? $( where $($trait : $bounds),* )? {}),
|
||||
stringify!($( < $( $lifetime_res )? $(, $generic_res : $bounds_res)* > )?),
|
||||
stringify!($($arg_res),*),
|
||||
stringify!($ret_res)
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fn test(
|
||||
op: &str,
|
||||
generics_expected: &str,
|
||||
args_expected: &str,
|
||||
return_expected: &str,
|
||||
) {
|
||||
// Parse the provided macro input as an ItemFn
|
||||
let item_fn = parse_str::<ItemFn>(op)
|
||||
.unwrap_or_else(|_| panic!("Failed to parse {op} as a ItemFn"));
|
||||
|
||||
let attrs = item_fn.attrs;
|
||||
let sig = parse_signature(attrs, item_fn.sig).unwrap_or_else(|err| {
|
||||
panic!("Failed to successfully parse signature from {op} ({err:?})")
|
||||
});
|
||||
println!("Raw parsed signatures = {sig:?}");
|
||||
|
||||
let mut generics_res = vec![];
|
||||
if let Some(lifetime) = sig.lifetime {
|
||||
generics_res.push(format!("'{lifetime}"));
|
||||
}
|
||||
for (name, bounds) in sig.generic_bounds {
|
||||
generics_res.push(format!("{name} : {bounds}"));
|
||||
}
|
||||
if !generics_res.is_empty() {
|
||||
assert_eq!(
|
||||
generics_expected,
|
||||
format!("< {} >", generics_res.join(", "))
|
||||
);
|
||||
}
|
||||
assert_eq!(
|
||||
args_expected,
|
||||
format!("{:?}", sig.args).trim_matches(|c| c == '[' || c == ']')
|
||||
);
|
||||
assert_eq!(return_expected, format!("{:?}", sig.ret_val));
|
||||
}
|
||||
|
||||
macro_rules! expect_fail {
|
||||
($name:ident, $error:expr, $f:item) => {
|
||||
#[test]
|
||||
pub fn $name() {
|
||||
expect_fail(stringify!($f), stringify!($error));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fn expect_fail(op: &str, error: &str) {
|
||||
// Parse the provided macro input as an ItemFn
|
||||
let item_fn = parse_str::<ItemFn>(op)
|
||||
.unwrap_or_else(|_| panic!("Failed to parse {op} as a ItemFn"));
|
||||
let attrs = item_fn.attrs;
|
||||
let err = parse_signature(attrs, item_fn.sig)
|
||||
.expect_err("Expected function to fail to parse");
|
||||
assert_eq!(format!("{err:?}"), error.to_owned());
|
||||
}
|
||||
|
||||
test!(
|
||||
fn op_state_and_number(opstate: &mut OpState, a: u32) -> ();
|
||||
(Ref(Mut, OpState), Numeric(u32)) -> Infallible(Void)
|
||||
);
|
||||
test!(
|
||||
fn op_slices(r#in: &[u8], out: &mut [u8]);
|
||||
(Slice(Ref, u8), Slice(Mut, u8)) -> Infallible(Void)
|
||||
);
|
||||
test!(
|
||||
#[serde] fn op_serde(#[serde] input: package::SerdeInputType) -> Result<package::SerdeReturnType, Error>;
|
||||
(SerdeV8("package::SerdeInputType")) -> Result(SerdeV8("package::SerdeReturnType"))
|
||||
);
|
||||
test!(
|
||||
fn op_local(input: v8::Local<v8::String>) -> Result<v8::Local<v8::String>, Error>;
|
||||
(V8Local(String)) -> Result(V8Local(String))
|
||||
);
|
||||
test!(
|
||||
fn op_resource(#[smi] rid: ResourceId, buffer: &[u8]);
|
||||
(Numeric(__SMI__), Slice(Ref, u8)) -> Infallible(Void)
|
||||
);
|
||||
test!(
|
||||
fn op_option_numeric_result(state: &mut OpState) -> Result<Option<u32>, AnyError>;
|
||||
(Ref(Mut, OpState)) -> Result(OptionNumeric(u32))
|
||||
);
|
||||
test!(
|
||||
fn op_ffi_read_f64(state: &mut OpState, ptr: * mut c_void, offset: isize) -> Result <f64, AnyError>;
|
||||
(Ref(Mut, OpState), Ptr(Mut, __VOID__), Numeric(isize)) -> Result(Numeric(f64))
|
||||
);
|
||||
test!(
|
||||
fn op_print(#[string] msg: &str, is_err: bool) -> Result<(), Error>;
|
||||
(Special(RefStr), Numeric(bool)) -> Result(Void)
|
||||
);
|
||||
test!(
|
||||
fn op_scope<'s>(#[string] msg: &'s str);
|
||||
<'s> (Special(RefStr)) -> Infallible(Void)
|
||||
);
|
||||
test!(
|
||||
fn op_scope_and_generics<'s, AB, BC>(#[string] msg: &'s str) where AB: some::Trait, BC: OtherTrait;
|
||||
<'s, AB: some::Trait, BC: OtherTrait> (Special(RefStr)) -> Infallible(Void)
|
||||
);
|
||||
|
||||
expect_fail!(op_with_two_lifetimes, TooManyLifetimes, fn f<'a, 'b>() {});
|
||||
expect_fail!(
|
||||
op_with_lifetime_bounds,
|
||||
LifetimesMayNotHaveBounds("'a"),
|
||||
fn f<'a: 'b, 'b>() {}
|
||||
);
|
||||
expect_fail!(
|
||||
op_with_missing_bounds,
|
||||
GenericBoundCardinality("B"),
|
||||
fn f<'a, B>() {}
|
||||
);
|
||||
expect_fail!(
|
||||
op_with_duplicate_bounds,
|
||||
GenericBoundCardinality("B"),
|
||||
fn f<'a, B: Trait>()
|
||||
where
|
||||
B: Trait,
|
||||
{
|
||||
}
|
||||
);
|
||||
expect_fail!(
|
||||
op_with_extra_bounds,
|
||||
WherePredicateMustAppearInGenerics("C"),
|
||||
fn f<'a, B>()
|
||||
where
|
||||
B: Trait,
|
||||
C: Trait,
|
||||
{
|
||||
}
|
||||
);
|
||||
|
||||
#[test]
|
||||
fn test_parse_result() {
|
||||
let rt = parse_str::<ReturnType>("-> Result < (), Error >")
|
||||
.expect("Failed to parse");
|
||||
println!("{:?}", parse_return(Attributes::default(), &rt));
|
||||
}
|
||||
}
|
|
@ -1,78 +0,0 @@
|
|||
#[allow(non_camel_case_types)]
|
||||
struct op_add {
|
||||
_unconstructable: ::std::marker::PhantomData<()>,
|
||||
}
|
||||
impl deno_core::_ops::Op for op_add {
|
||||
const NAME: &'static str = stringify!(op_add);
|
||||
const DECL: deno_core::_ops::OpDecl = deno_core::_ops::OpDecl {
|
||||
name: stringify!(op_add),
|
||||
v8_fn_ptr: Self::v8_fn_ptr as _,
|
||||
enabled: true,
|
||||
fast_fn: Some({
|
||||
use deno_core::v8::fast_api::Type;
|
||||
use deno_core::v8::fast_api::CType;
|
||||
deno_core::v8::fast_api::FastFunction::new(
|
||||
&[Type::V8Value, Type::Uint32, Type::Uint32],
|
||||
CType::Uint32,
|
||||
Self::v8_fn_ptr_fast as *const ::std::ffi::c_void,
|
||||
)
|
||||
}),
|
||||
is_async: false,
|
||||
is_unstable: false,
|
||||
is_v8: false,
|
||||
arg_count: 2usize as u8,
|
||||
};
|
||||
}
|
||||
impl op_add {
|
||||
pub const fn name() -> &'static str {
|
||||
stringify!(op_add)
|
||||
}
|
||||
pub const fn decl() -> deno_core::_ops::OpDecl {
|
||||
deno_core::_ops::OpDecl {
|
||||
name: stringify!(op_add),
|
||||
v8_fn_ptr: Self::v8_fn_ptr as _,
|
||||
enabled: true,
|
||||
fast_fn: Some({
|
||||
use deno_core::v8::fast_api::Type;
|
||||
use deno_core::v8::fast_api::CType;
|
||||
deno_core::v8::fast_api::FastFunction::new(
|
||||
&[Type::V8Value, Type::Uint32, Type::Uint32],
|
||||
CType::Uint32,
|
||||
Self::v8_fn_ptr_fast as *const ::std::ffi::c_void,
|
||||
)
|
||||
}),
|
||||
is_async: false,
|
||||
is_unstable: false,
|
||||
is_v8: false,
|
||||
arg_count: 2usize as u8,
|
||||
}
|
||||
}
|
||||
fn v8_fn_ptr_fast(
|
||||
_: deno_core::v8::Local<deno_core::v8::Object>,
|
||||
arg0: u32,
|
||||
arg1: u32,
|
||||
) -> u32 {
|
||||
let arg0 = arg0 as _;
|
||||
let arg1 = arg1 as _;
|
||||
let result = Self::call(arg0, arg1);
|
||||
result
|
||||
}
|
||||
extern "C" fn v8_fn_ptr(info: *const deno_core::v8::FunctionCallbackInfo) {
|
||||
let mut rv = deno_core::v8::ReturnValue::from_function_callback_info(unsafe {
|
||||
&*info
|
||||
});
|
||||
let args = deno_core::v8::FunctionCallbackArguments::from_function_callback_info(unsafe {
|
||||
&*info
|
||||
});
|
||||
let arg0 = args.get(0usize as i32);
|
||||
let arg0 = deno_core::_ops::to_u32(&arg0) as _;
|
||||
let arg1 = args.get(1usize as i32);
|
||||
let arg1 = deno_core::_ops::to_u32(&arg1) as _;
|
||||
let result = Self::call(arg0, arg1);
|
||||
rv.set_uint32(result as u32);
|
||||
}
|
||||
#[inline(always)]
|
||||
fn call(a: u32, b: u32) -> u32 {
|
||||
a + b
|
||||
}
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
||||
|
||||
#[op2(fast)]
|
||||
fn op_add(a: u32, b: u32) -> u32 {
|
||||
a + b
|
||||
}
|
|
@ -1,57 +0,0 @@
|
|||
#[allow(non_camel_case_types)]
|
||||
pub struct op_test_add_option {
|
||||
_unconstructable: ::std::marker::PhantomData<()>,
|
||||
}
|
||||
impl crate::_ops::Op for op_test_add_option {
|
||||
const NAME: &'static str = stringify!(op_test_add_option);
|
||||
const DECL: crate::_ops::OpDecl = crate::_ops::OpDecl {
|
||||
name: stringify!(op_test_add_option),
|
||||
v8_fn_ptr: Self::v8_fn_ptr as _,
|
||||
enabled: true,
|
||||
fast_fn: None,
|
||||
is_async: false,
|
||||
is_unstable: false,
|
||||
is_v8: false,
|
||||
arg_count: 2usize as u8,
|
||||
};
|
||||
}
|
||||
impl op_test_add_option {
|
||||
pub const fn name() -> &'static str {
|
||||
stringify!(op_test_add_option)
|
||||
}
|
||||
pub const fn decl() -> crate::_ops::OpDecl {
|
||||
crate::_ops::OpDecl {
|
||||
name: stringify!(op_test_add_option),
|
||||
v8_fn_ptr: Self::v8_fn_ptr as _,
|
||||
enabled: true,
|
||||
fast_fn: None,
|
||||
is_async: false,
|
||||
is_unstable: false,
|
||||
is_v8: false,
|
||||
arg_count: 2usize as u8,
|
||||
}
|
||||
}
|
||||
extern "C" fn v8_fn_ptr(info: *const crate::v8::FunctionCallbackInfo) {
|
||||
let mut rv = crate::v8::ReturnValue::from_function_callback_info(unsafe {
|
||||
&*info
|
||||
});
|
||||
let args = crate::v8::FunctionCallbackArguments::from_function_callback_info(unsafe {
|
||||
&*info
|
||||
});
|
||||
let arg0 = args.get(0usize as i32);
|
||||
let arg0 = crate::_ops::to_u32(&arg0) as _;
|
||||
let arg1 = args.get(1usize as i32);
|
||||
let arg1 = if arg1.is_null_or_undefined() {
|
||||
None
|
||||
} else {
|
||||
let arg1 = crate::_ops::to_u32(&arg1) as _;
|
||||
Some(arg1)
|
||||
};
|
||||
let result = Self::call(arg0, arg1);
|
||||
rv.set_uint32(result as u32);
|
||||
}
|
||||
#[inline(always)]
|
||||
pub fn call(a: u32, b: Option<u32>) -> u32 {
|
||||
a + b.unwrap_or(100)
|
||||
}
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
||||
|
||||
#[op2(core)]
|
||||
pub fn op_test_add_option(a: u32, b: Option<u32>) -> u32 {
|
||||
a + b.unwrap_or(100)
|
||||
}
|
|
@ -1,59 +0,0 @@
|
|||
#[allow(non_camel_case_types)]
|
||||
pub struct op_has_doc_comment {
|
||||
_unconstructable: ::std::marker::PhantomData<()>,
|
||||
}
|
||||
impl deno_core::_ops::Op for op_has_doc_comment {
|
||||
const NAME: &'static str = stringify!(op_has_doc_comment);
|
||||
const DECL: deno_core::_ops::OpDecl = deno_core::_ops::OpDecl {
|
||||
name: stringify!(op_has_doc_comment),
|
||||
v8_fn_ptr: Self::v8_fn_ptr as _,
|
||||
enabled: true,
|
||||
fast_fn: Some({
|
||||
use deno_core::v8::fast_api::Type;
|
||||
use deno_core::v8::fast_api::CType;
|
||||
deno_core::v8::fast_api::FastFunction::new(
|
||||
&[Type::V8Value],
|
||||
CType::Void,
|
||||
Self::v8_fn_ptr_fast as *const ::std::ffi::c_void,
|
||||
)
|
||||
}),
|
||||
is_async: false,
|
||||
is_unstable: false,
|
||||
is_v8: false,
|
||||
arg_count: 0usize as u8,
|
||||
};
|
||||
}
|
||||
impl op_has_doc_comment {
|
||||
pub const fn name() -> &'static str {
|
||||
stringify!(op_has_doc_comment)
|
||||
}
|
||||
pub const fn decl() -> deno_core::_ops::OpDecl {
|
||||
deno_core::_ops::OpDecl {
|
||||
name: stringify!(op_has_doc_comment),
|
||||
v8_fn_ptr: Self::v8_fn_ptr as _,
|
||||
enabled: true,
|
||||
fast_fn: Some({
|
||||
use deno_core::v8::fast_api::Type;
|
||||
use deno_core::v8::fast_api::CType;
|
||||
deno_core::v8::fast_api::FastFunction::new(
|
||||
&[Type::V8Value],
|
||||
CType::Void,
|
||||
Self::v8_fn_ptr_fast as *const ::std::ffi::c_void,
|
||||
)
|
||||
}),
|
||||
is_async: false,
|
||||
is_unstable: false,
|
||||
is_v8: false,
|
||||
arg_count: 0usize as u8,
|
||||
}
|
||||
}
|
||||
fn v8_fn_ptr_fast(_: deno_core::v8::Local<deno_core::v8::Object>) -> () {
|
||||
let result = Self::call();
|
||||
result
|
||||
}
|
||||
extern "C" fn v8_fn_ptr(info: *const deno_core::v8::FunctionCallbackInfo) {
|
||||
let result = Self::call();
|
||||
}
|
||||
#[inline(always)]
|
||||
pub fn call() -> () {}
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
||||
|
||||
/// This is a doc comment.
|
||||
#[op2(fast)]
|
||||
pub fn op_has_doc_comment() -> () {}
|
|
@ -1,59 +0,0 @@
|
|||
#[allow(non_camel_case_types)]
|
||||
pub struct op_generics<T> {
|
||||
_unconstructable: ::std::marker::PhantomData<(T)>,
|
||||
}
|
||||
impl<T: Trait> deno_core::_ops::Op for op_generics<T> {
|
||||
const NAME: &'static str = stringify!(op_generics);
|
||||
const DECL: deno_core::_ops::OpDecl = deno_core::_ops::OpDecl {
|
||||
name: stringify!(op_generics),
|
||||
v8_fn_ptr: Self::v8_fn_ptr as _,
|
||||
enabled: true,
|
||||
fast_fn: Some({
|
||||
use deno_core::v8::fast_api::Type;
|
||||
use deno_core::v8::fast_api::CType;
|
||||
deno_core::v8::fast_api::FastFunction::new(
|
||||
&[Type::V8Value],
|
||||
CType::Void,
|
||||
Self::v8_fn_ptr_fast as *const ::std::ffi::c_void,
|
||||
)
|
||||
}),
|
||||
is_async: false,
|
||||
is_unstable: false,
|
||||
is_v8: false,
|
||||
arg_count: 0usize as u8,
|
||||
};
|
||||
}
|
||||
impl<T: Trait> op_generics<T> {
|
||||
pub const fn name() -> &'static str {
|
||||
stringify!(op_generics)
|
||||
}
|
||||
pub const fn decl() -> deno_core::_ops::OpDecl {
|
||||
deno_core::_ops::OpDecl {
|
||||
name: stringify!(op_generics),
|
||||
v8_fn_ptr: Self::v8_fn_ptr as _,
|
||||
enabled: true,
|
||||
fast_fn: Some({
|
||||
use deno_core::v8::fast_api::Type;
|
||||
use deno_core::v8::fast_api::CType;
|
||||
deno_core::v8::fast_api::FastFunction::new(
|
||||
&[Type::V8Value],
|
||||
CType::Void,
|
||||
Self::v8_fn_ptr_fast as *const ::std::ffi::c_void,
|
||||
)
|
||||
}),
|
||||
is_async: false,
|
||||
is_unstable: false,
|
||||
is_v8: false,
|
||||
arg_count: 0usize as u8,
|
||||
}
|
||||
}
|
||||
fn v8_fn_ptr_fast(_: deno_core::v8::Local<deno_core::v8::Object>) -> () {
|
||||
let result = Self::call();
|
||||
result
|
||||
}
|
||||
extern "C" fn v8_fn_ptr(info: *const deno_core::v8::FunctionCallbackInfo) {
|
||||
let result = Self::call();
|
||||
}
|
||||
#[inline(always)]
|
||||
pub fn call() {}
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
||||
|
||||
#[op2(fast)]
|
||||
pub fn op_generics<T: Trait>() {}
|
|
@ -1,122 +0,0 @@
|
|||
#[allow(non_camel_case_types)]
|
||||
pub struct op_u32_with_result {
|
||||
_unconstructable: ::std::marker::PhantomData<()>,
|
||||
}
|
||||
impl deno_core::_ops::Op for op_u32_with_result {
|
||||
const NAME: &'static str = stringify!(op_u32_with_result);
|
||||
const DECL: deno_core::_ops::OpDecl = deno_core::_ops::OpDecl {
|
||||
name: stringify!(op_u32_with_result),
|
||||
v8_fn_ptr: Self::v8_fn_ptr as _,
|
||||
enabled: true,
|
||||
fast_fn: Some({
|
||||
use deno_core::v8::fast_api::Type;
|
||||
use deno_core::v8::fast_api::CType;
|
||||
deno_core::v8::fast_api::FastFunction::new(
|
||||
&[Type::V8Value, Type::CallbackOptions],
|
||||
CType::Uint32,
|
||||
Self::v8_fn_ptr_fast as *const ::std::ffi::c_void,
|
||||
)
|
||||
}),
|
||||
is_async: false,
|
||||
is_unstable: false,
|
||||
is_v8: false,
|
||||
arg_count: 0usize as u8,
|
||||
};
|
||||
}
|
||||
impl op_u32_with_result {
|
||||
pub const fn name() -> &'static str {
|
||||
stringify!(op_u32_with_result)
|
||||
}
|
||||
pub const fn decl() -> deno_core::_ops::OpDecl {
|
||||
deno_core::_ops::OpDecl {
|
||||
name: stringify!(op_u32_with_result),
|
||||
v8_fn_ptr: Self::v8_fn_ptr as _,
|
||||
enabled: true,
|
||||
fast_fn: Some({
|
||||
use deno_core::v8::fast_api::Type;
|
||||
use deno_core::v8::fast_api::CType;
|
||||
deno_core::v8::fast_api::FastFunction::new(
|
||||
&[Type::V8Value, Type::CallbackOptions],
|
||||
CType::Uint32,
|
||||
Self::v8_fn_ptr_fast as *const ::std::ffi::c_void,
|
||||
)
|
||||
}),
|
||||
is_async: false,
|
||||
is_unstable: false,
|
||||
is_v8: false,
|
||||
arg_count: 0usize as u8,
|
||||
}
|
||||
}
|
||||
fn v8_fn_ptr_fast(
|
||||
_: deno_core::v8::Local<deno_core::v8::Object>,
|
||||
fast_api_callback_options: *mut deno_core::v8::fast_api::FastApiCallbackOptions,
|
||||
) -> u32 {
|
||||
let fast_api_callback_options = unsafe { &mut *fast_api_callback_options };
|
||||
let opctx = unsafe {
|
||||
&*(deno_core::v8::Local::<
|
||||
v8::External,
|
||||
>::cast(unsafe { fast_api_callback_options.data.data })
|
||||
.value() as *const deno_core::_ops::OpCtx)
|
||||
};
|
||||
let result = Self::call();
|
||||
let result = match result {
|
||||
Ok(result) => result,
|
||||
Err(err) => {
|
||||
unsafe {
|
||||
opctx.unsafely_set_last_error_for_ops_only(err);
|
||||
}
|
||||
fast_api_callback_options.fallback = true;
|
||||
return ::std::default::Default::default();
|
||||
}
|
||||
};
|
||||
result
|
||||
}
|
||||
extern "C" fn v8_fn_ptr(info: *const deno_core::v8::FunctionCallbackInfo) {
|
||||
let mut rv = deno_core::v8::ReturnValue::from_function_callback_info(unsafe {
|
||||
&*info
|
||||
});
|
||||
let args = deno_core::v8::FunctionCallbackArguments::from_function_callback_info(unsafe {
|
||||
&*info
|
||||
});
|
||||
let opctx = unsafe {
|
||||
&*(deno_core::v8::Local::<deno_core::v8::External>::cast(args.data()).value()
|
||||
as *const deno_core::_ops::OpCtx)
|
||||
};
|
||||
if let Some(err) = unsafe { opctx.unsafely_take_last_error_for_ops_only() } {
|
||||
let scope = &mut unsafe { deno_core::v8::CallbackScope::new(&*info) };
|
||||
let args = deno_core::v8::FunctionCallbackArguments::from_function_callback_info(unsafe {
|
||||
&*info
|
||||
});
|
||||
let opstate = ::std::cell::RefCell::borrow(&*opctx.state);
|
||||
let exception = deno_core::error::to_v8_error(
|
||||
scope,
|
||||
opstate.get_error_class_fn,
|
||||
&err,
|
||||
);
|
||||
scope.throw_exception(exception);
|
||||
return;
|
||||
}
|
||||
let result = Self::call();
|
||||
match result {
|
||||
Ok(result) => {
|
||||
rv.set_uint32(result as u32);
|
||||
}
|
||||
Err(err) => {
|
||||
let scope = &mut unsafe { deno_core::v8::CallbackScope::new(&*info) };
|
||||
let args = deno_core::v8::FunctionCallbackArguments::from_function_callback_info(unsafe {
|
||||
&*info
|
||||
});
|
||||
let opstate = ::std::cell::RefCell::borrow(&*opctx.state);
|
||||
let exception = deno_core::error::to_v8_error(
|
||||
scope,
|
||||
opstate.get_error_class_fn,
|
||||
&err,
|
||||
);
|
||||
scope.throw_exception(exception);
|
||||
return;
|
||||
}
|
||||
};
|
||||
}
|
||||
#[inline(always)]
|
||||
pub fn call() -> Result<u32, AnyError> {}
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
||||
|
||||
#[op2(fast)]
|
||||
pub fn op_u32_with_result() -> Result<u32, AnyError> {}
|
|
@ -1,117 +0,0 @@
|
|||
#[allow(non_camel_case_types)]
|
||||
pub struct op_void_with_result {
|
||||
_unconstructable: ::std::marker::PhantomData<()>,
|
||||
}
|
||||
impl deno_core::_ops::Op for op_void_with_result {
|
||||
const NAME: &'static str = stringify!(op_void_with_result);
|
||||
const DECL: deno_core::_ops::OpDecl = deno_core::_ops::OpDecl {
|
||||
name: stringify!(op_void_with_result),
|
||||
v8_fn_ptr: Self::v8_fn_ptr as _,
|
||||
enabled: true,
|
||||
fast_fn: Some({
|
||||
use deno_core::v8::fast_api::Type;
|
||||
use deno_core::v8::fast_api::CType;
|
||||
deno_core::v8::fast_api::FastFunction::new(
|
||||
&[Type::V8Value, Type::CallbackOptions],
|
||||
CType::Void,
|
||||
Self::v8_fn_ptr_fast as *const ::std::ffi::c_void,
|
||||
)
|
||||
}),
|
||||
is_async: false,
|
||||
is_unstable: false,
|
||||
is_v8: false,
|
||||
arg_count: 0usize as u8,
|
||||
};
|
||||
}
|
||||
impl op_void_with_result {
|
||||
pub const fn name() -> &'static str {
|
||||
stringify!(op_void_with_result)
|
||||
}
|
||||
pub const fn decl() -> deno_core::_ops::OpDecl {
|
||||
deno_core::_ops::OpDecl {
|
||||
name: stringify!(op_void_with_result),
|
||||
v8_fn_ptr: Self::v8_fn_ptr as _,
|
||||
enabled: true,
|
||||
fast_fn: Some({
|
||||
use deno_core::v8::fast_api::Type;
|
||||
use deno_core::v8::fast_api::CType;
|
||||
deno_core::v8::fast_api::FastFunction::new(
|
||||
&[Type::V8Value, Type::CallbackOptions],
|
||||
CType::Void,
|
||||
Self::v8_fn_ptr_fast as *const ::std::ffi::c_void,
|
||||
)
|
||||
}),
|
||||
is_async: false,
|
||||
is_unstable: false,
|
||||
is_v8: false,
|
||||
arg_count: 0usize as u8,
|
||||
}
|
||||
}
|
||||
fn v8_fn_ptr_fast(
|
||||
_: deno_core::v8::Local<deno_core::v8::Object>,
|
||||
fast_api_callback_options: *mut deno_core::v8::fast_api::FastApiCallbackOptions,
|
||||
) -> () {
|
||||
let fast_api_callback_options = unsafe { &mut *fast_api_callback_options };
|
||||
let opctx = unsafe {
|
||||
&*(deno_core::v8::Local::<
|
||||
v8::External,
|
||||
>::cast(unsafe { fast_api_callback_options.data.data })
|
||||
.value() as *const deno_core::_ops::OpCtx)
|
||||
};
|
||||
let result = Self::call();
|
||||
let result = match result {
|
||||
Ok(result) => result,
|
||||
Err(err) => {
|
||||
unsafe {
|
||||
opctx.unsafely_set_last_error_for_ops_only(err);
|
||||
}
|
||||
fast_api_callback_options.fallback = true;
|
||||
return ::std::default::Default::default();
|
||||
}
|
||||
};
|
||||
result
|
||||
}
|
||||
extern "C" fn v8_fn_ptr(info: *const deno_core::v8::FunctionCallbackInfo) {
|
||||
let args = deno_core::v8::FunctionCallbackArguments::from_function_callback_info(unsafe {
|
||||
&*info
|
||||
});
|
||||
let opctx = unsafe {
|
||||
&*(deno_core::v8::Local::<deno_core::v8::External>::cast(args.data()).value()
|
||||
as *const deno_core::_ops::OpCtx)
|
||||
};
|
||||
if let Some(err) = unsafe { opctx.unsafely_take_last_error_for_ops_only() } {
|
||||
let scope = &mut unsafe { deno_core::v8::CallbackScope::new(&*info) };
|
||||
let args = deno_core::v8::FunctionCallbackArguments::from_function_callback_info(unsafe {
|
||||
&*info
|
||||
});
|
||||
let opstate = ::std::cell::RefCell::borrow(&*opctx.state);
|
||||
let exception = deno_core::error::to_v8_error(
|
||||
scope,
|
||||
opstate.get_error_class_fn,
|
||||
&err,
|
||||
);
|
||||
scope.throw_exception(exception);
|
||||
return;
|
||||
}
|
||||
let result = Self::call();
|
||||
match result {
|
||||
Ok(result) => {}
|
||||
Err(err) => {
|
||||
let scope = &mut unsafe { deno_core::v8::CallbackScope::new(&*info) };
|
||||
let args = deno_core::v8::FunctionCallbackArguments::from_function_callback_info(unsafe {
|
||||
&*info
|
||||
});
|
||||
let opstate = ::std::cell::RefCell::borrow(&*opctx.state);
|
||||
let exception = deno_core::error::to_v8_error(
|
||||
scope,
|
||||
opstate.get_error_class_fn,
|
||||
&err,
|
||||
);
|
||||
scope.throw_exception(exception);
|
||||
return;
|
||||
}
|
||||
};
|
||||
}
|
||||
#[inline(always)]
|
||||
pub fn call() -> Result<(), AnyError> {}
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
||||
|
||||
#[op2(fast)]
|
||||
pub fn op_void_with_result() -> Result<(), AnyError> {}
|
|
@ -1,76 +0,0 @@
|
|||
#[allow(non_camel_case_types)]
|
||||
struct op_add {
|
||||
_unconstructable: ::std::marker::PhantomData<()>,
|
||||
}
|
||||
impl deno_core::_ops::Op for op_add {
|
||||
const NAME: &'static str = stringify!(op_add);
|
||||
const DECL: deno_core::_ops::OpDecl = deno_core::_ops::OpDecl {
|
||||
name: stringify!(op_add),
|
||||
v8_fn_ptr: Self::v8_fn_ptr as _,
|
||||
enabled: true,
|
||||
fast_fn: Some({
|
||||
use deno_core::v8::fast_api::Type;
|
||||
use deno_core::v8::fast_api::CType;
|
||||
deno_core::v8::fast_api::FastFunction::new(
|
||||
&[Type::V8Value, Type::Int32, Type::Uint32],
|
||||
CType::Uint32,
|
||||
Self::v8_fn_ptr_fast as *const ::std::ffi::c_void,
|
||||
)
|
||||
}),
|
||||
is_async: false,
|
||||
is_unstable: false,
|
||||
is_v8: false,
|
||||
arg_count: 2usize as u8,
|
||||
};
|
||||
}
|
||||
impl op_add {
|
||||
pub const fn name() -> &'static str {
|
||||
stringify!(op_add)
|
||||
}
|
||||
pub const fn decl() -> deno_core::_ops::OpDecl {
|
||||
deno_core::_ops::OpDecl {
|
||||
name: stringify!(op_add),
|
||||
v8_fn_ptr: Self::v8_fn_ptr as _,
|
||||
enabled: true,
|
||||
fast_fn: Some({
|
||||
use deno_core::v8::fast_api::Type;
|
||||
use deno_core::v8::fast_api::CType;
|
||||
deno_core::v8::fast_api::FastFunction::new(
|
||||
&[Type::V8Value, Type::Int32, Type::Uint32],
|
||||
CType::Uint32,
|
||||
Self::v8_fn_ptr_fast as *const ::std::ffi::c_void,
|
||||
)
|
||||
}),
|
||||
is_async: false,
|
||||
is_unstable: false,
|
||||
is_v8: false,
|
||||
arg_count: 2usize as u8,
|
||||
}
|
||||
}
|
||||
fn v8_fn_ptr_fast(
|
||||
_: deno_core::v8::Local<deno_core::v8::Object>,
|
||||
arg0: i32,
|
||||
arg1: u32,
|
||||
) -> u32 {
|
||||
let arg0 = arg0 as _;
|
||||
let arg1 = arg1 as _;
|
||||
let result = Self::call(arg0, arg1);
|
||||
result
|
||||
}
|
||||
extern "C" fn v8_fn_ptr(info: *const deno_core::v8::FunctionCallbackInfo) {
|
||||
let mut rv = deno_core::v8::ReturnValue::from_function_callback_info(unsafe {
|
||||
&*info
|
||||
});
|
||||
let args = deno_core::v8::FunctionCallbackArguments::from_function_callback_info(unsafe {
|
||||
&*info
|
||||
});
|
||||
let arg0 = args.get(0usize as i32);
|
||||
let arg0 = deno_core::_ops::to_i32(&arg0) as _;
|
||||
let arg1 = args.get(1usize as i32);
|
||||
let arg1 = deno_core::_ops::to_u32(&arg1) as _;
|
||||
let result = Self::call(arg0, arg1);
|
||||
rv.set_uint32(result as u32);
|
||||
}
|
||||
#[inline(always)]
|
||||
fn call(id: ResourceId, extra: u16) -> u32 {}
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
||||
|
||||
#[op2(fast)]
|
||||
fn op_add(#[smi] id: ResourceId, extra: u16) -> u32 {}
|
|
@ -1,75 +0,0 @@
|
|||
#[allow(non_camel_case_types)]
|
||||
struct op_string_cow {
|
||||
_unconstructable: ::std::marker::PhantomData<()>,
|
||||
}
|
||||
impl deno_core::_ops::Op for op_string_cow {
|
||||
const NAME: &'static str = stringify!(op_string_cow);
|
||||
const DECL: deno_core::_ops::OpDecl = deno_core::_ops::OpDecl {
|
||||
name: stringify!(op_string_cow),
|
||||
v8_fn_ptr: Self::v8_fn_ptr as _,
|
||||
enabled: true,
|
||||
fast_fn: Some({
|
||||
use deno_core::v8::fast_api::Type;
|
||||
use deno_core::v8::fast_api::CType;
|
||||
deno_core::v8::fast_api::FastFunction::new(
|
||||
&[Type::V8Value, Type::SeqOneByteString],
|
||||
CType::Uint32,
|
||||
Self::v8_fn_ptr_fast as *const ::std::ffi::c_void,
|
||||
)
|
||||
}),
|
||||
is_async: false,
|
||||
is_unstable: false,
|
||||
is_v8: false,
|
||||
arg_count: 1usize as u8,
|
||||
};
|
||||
}
|
||||
impl op_string_cow {
|
||||
pub const fn name() -> &'static str {
|
||||
stringify!(op_string_cow)
|
||||
}
|
||||
pub const fn decl() -> deno_core::_ops::OpDecl {
|
||||
deno_core::_ops::OpDecl {
|
||||
name: stringify!(op_string_cow),
|
||||
v8_fn_ptr: Self::v8_fn_ptr as _,
|
||||
enabled: true,
|
||||
fast_fn: Some({
|
||||
use deno_core::v8::fast_api::Type;
|
||||
use deno_core::v8::fast_api::CType;
|
||||
deno_core::v8::fast_api::FastFunction::new(
|
||||
&[Type::V8Value, Type::SeqOneByteString],
|
||||
CType::Uint32,
|
||||
Self::v8_fn_ptr_fast as *const ::std::ffi::c_void,
|
||||
)
|
||||
}),
|
||||
is_async: false,
|
||||
is_unstable: false,
|
||||
is_v8: false,
|
||||
arg_count: 1usize as u8,
|
||||
}
|
||||
}
|
||||
fn v8_fn_ptr_fast(
|
||||
_: deno_core::v8::Local<deno_core::v8::Object>,
|
||||
arg0: *mut deno_core::v8::fast_api::FastApiOneByteString,
|
||||
) -> u32 {
|
||||
let mut arg0_temp: [::std::mem::MaybeUninit<u8>; 1024] = [::std::mem::MaybeUninit::uninit(); 1024];
|
||||
let arg0 = deno_core::_ops::to_str_ptr(unsafe { &mut *arg0 }, &mut arg0_temp);
|
||||
let result = Self::call(arg0);
|
||||
result
|
||||
}
|
||||
extern "C" fn v8_fn_ptr(info: *const deno_core::v8::FunctionCallbackInfo) {
|
||||
let scope = &mut unsafe { deno_core::v8::CallbackScope::new(&*info) };
|
||||
let mut rv = deno_core::v8::ReturnValue::from_function_callback_info(unsafe {
|
||||
&*info
|
||||
});
|
||||
let args = deno_core::v8::FunctionCallbackArguments::from_function_callback_info(unsafe {
|
||||
&*info
|
||||
});
|
||||
let arg0 = args.get(0usize as i32);
|
||||
let mut arg0_temp: [::std::mem::MaybeUninit<u8>; 1024] = [::std::mem::MaybeUninit::uninit(); 1024];
|
||||
let arg0 = deno_core::_ops::to_str(scope, &arg0, &mut arg0_temp);
|
||||
let result = Self::call(arg0);
|
||||
rv.set_uint32(result as u32);
|
||||
}
|
||||
#[inline(always)]
|
||||
fn call(s: Cow<str>) -> u32 {}
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
||||
|
||||
#[op2(fast)]
|
||||
fn op_string_cow(#[string] s: Cow<str>) -> u32 {}
|
|
@ -1,53 +0,0 @@
|
|||
#[allow(non_camel_case_types)]
|
||||
pub struct op_string_return {
|
||||
_unconstructable: ::std::marker::PhantomData<()>,
|
||||
}
|
||||
impl deno_core::_ops::Op for op_string_return {
|
||||
const NAME: &'static str = stringify!(op_string_return);
|
||||
const DECL: deno_core::_ops::OpDecl = deno_core::_ops::OpDecl {
|
||||
name: stringify!(op_string_return),
|
||||
v8_fn_ptr: Self::v8_fn_ptr as _,
|
||||
enabled: true,
|
||||
fast_fn: None,
|
||||
is_async: false,
|
||||
is_unstable: false,
|
||||
is_v8: false,
|
||||
arg_count: 0usize as u8,
|
||||
};
|
||||
}
|
||||
impl op_string_return {
|
||||
pub const fn name() -> &'static str {
|
||||
stringify!(op_string_return)
|
||||
}
|
||||
pub const fn decl() -> deno_core::_ops::OpDecl {
|
||||
deno_core::_ops::OpDecl {
|
||||
name: stringify!(op_string_return),
|
||||
v8_fn_ptr: Self::v8_fn_ptr as _,
|
||||
enabled: true,
|
||||
fast_fn: None,
|
||||
is_async: false,
|
||||
is_unstable: false,
|
||||
is_v8: false,
|
||||
arg_count: 0usize as u8,
|
||||
}
|
||||
}
|
||||
extern "C" fn v8_fn_ptr(info: *const deno_core::v8::FunctionCallbackInfo) {
|
||||
let scope = &mut unsafe { deno_core::v8::CallbackScope::new(&*info) };
|
||||
let mut rv = deno_core::v8::ReturnValue::from_function_callback_info(unsafe {
|
||||
&*info
|
||||
});
|
||||
let result = Self::call();
|
||||
if let Some(result) = result {
|
||||
if result.is_empty() {
|
||||
rv.set_empty_string();
|
||||
} else {
|
||||
let temp = deno_core::v8::String::new(scope, &result).unwrap();
|
||||
rv.set(temp.into());
|
||||
}
|
||||
} else {
|
||||
rv.set_null();
|
||||
}
|
||||
}
|
||||
#[inline(always)]
|
||||
pub fn call() -> Option<String> {}
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
||||
|
||||
#[op2]
|
||||
#[string]
|
||||
pub fn op_string_return() -> Option<String> {}
|
|
@ -1,73 +0,0 @@
|
|||
#[allow(non_camel_case_types)]
|
||||
struct op_string_owned {
|
||||
_unconstructable: ::std::marker::PhantomData<()>,
|
||||
}
|
||||
impl deno_core::_ops::Op for op_string_owned {
|
||||
const NAME: &'static str = stringify!(op_string_owned);
|
||||
const DECL: deno_core::_ops::OpDecl = deno_core::_ops::OpDecl {
|
||||
name: stringify!(op_string_owned),
|
||||
v8_fn_ptr: Self::v8_fn_ptr as _,
|
||||
enabled: true,
|
||||
fast_fn: Some({
|
||||
use deno_core::v8::fast_api::Type;
|
||||
use deno_core::v8::fast_api::CType;
|
||||
deno_core::v8::fast_api::FastFunction::new(
|
||||
&[Type::V8Value, Type::SeqOneByteString],
|
||||
CType::Uint32,
|
||||
Self::v8_fn_ptr_fast as *const ::std::ffi::c_void,
|
||||
)
|
||||
}),
|
||||
is_async: false,
|
||||
is_unstable: false,
|
||||
is_v8: false,
|
||||
arg_count: 1usize as u8,
|
||||
};
|
||||
}
|
||||
impl op_string_owned {
|
||||
pub const fn name() -> &'static str {
|
||||
stringify!(op_string_owned)
|
||||
}
|
||||
pub const fn decl() -> deno_core::_ops::OpDecl {
|
||||
deno_core::_ops::OpDecl {
|
||||
name: stringify!(op_string_owned),
|
||||
v8_fn_ptr: Self::v8_fn_ptr as _,
|
||||
enabled: true,
|
||||
fast_fn: Some({
|
||||
use deno_core::v8::fast_api::Type;
|
||||
use deno_core::v8::fast_api::CType;
|
||||
deno_core::v8::fast_api::FastFunction::new(
|
||||
&[Type::V8Value, Type::SeqOneByteString],
|
||||
CType::Uint32,
|
||||
Self::v8_fn_ptr_fast as *const ::std::ffi::c_void,
|
||||
)
|
||||
}),
|
||||
is_async: false,
|
||||
is_unstable: false,
|
||||
is_v8: false,
|
||||
arg_count: 1usize as u8,
|
||||
}
|
||||
}
|
||||
fn v8_fn_ptr_fast(
|
||||
_: deno_core::v8::Local<deno_core::v8::Object>,
|
||||
arg0: *mut deno_core::v8::fast_api::FastApiOneByteString,
|
||||
) -> u32 {
|
||||
let arg0 = deno_core::_ops::to_string_ptr(unsafe { &mut *arg0 });
|
||||
let result = Self::call(arg0);
|
||||
result
|
||||
}
|
||||
extern "C" fn v8_fn_ptr(info: *const deno_core::v8::FunctionCallbackInfo) {
|
||||
let scope = &mut unsafe { deno_core::v8::CallbackScope::new(&*info) };
|
||||
let mut rv = deno_core::v8::ReturnValue::from_function_callback_info(unsafe {
|
||||
&*info
|
||||
});
|
||||
let args = deno_core::v8::FunctionCallbackArguments::from_function_callback_info(unsafe {
|
||||
&*info
|
||||
});
|
||||
let arg0 = args.get(0usize as i32);
|
||||
let arg0 = arg0.to_rust_string_lossy(scope);
|
||||
let result = Self::call(arg0);
|
||||
rv.set_uint32(result as u32);
|
||||
}
|
||||
#[inline(always)]
|
||||
fn call(s: String) -> u32 {}
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
||||
|
||||
#[op2(fast)]
|
||||
fn op_string_owned(#[string] s: String) -> u32 {}
|
|
@ -1,75 +0,0 @@
|
|||
#[allow(non_camel_case_types)]
|
||||
struct op_string_owned {
|
||||
_unconstructable: ::std::marker::PhantomData<()>,
|
||||
}
|
||||
impl deno_core::_ops::Op for op_string_owned {
|
||||
const NAME: &'static str = stringify!(op_string_owned);
|
||||
const DECL: deno_core::_ops::OpDecl = deno_core::_ops::OpDecl {
|
||||
name: stringify!(op_string_owned),
|
||||
v8_fn_ptr: Self::v8_fn_ptr as _,
|
||||
enabled: true,
|
||||
fast_fn: Some({
|
||||
use deno_core::v8::fast_api::Type;
|
||||
use deno_core::v8::fast_api::CType;
|
||||
deno_core::v8::fast_api::FastFunction::new(
|
||||
&[Type::V8Value, Type::SeqOneByteString],
|
||||
CType::Uint32,
|
||||
Self::v8_fn_ptr_fast as *const ::std::ffi::c_void,
|
||||
)
|
||||
}),
|
||||
is_async: false,
|
||||
is_unstable: false,
|
||||
is_v8: false,
|
||||
arg_count: 1usize as u8,
|
||||
};
|
||||
}
|
||||
impl op_string_owned {
|
||||
pub const fn name() -> &'static str {
|
||||
stringify!(op_string_owned)
|
||||
}
|
||||
pub const fn decl() -> deno_core::_ops::OpDecl {
|
||||
deno_core::_ops::OpDecl {
|
||||
name: stringify!(op_string_owned),
|
||||
v8_fn_ptr: Self::v8_fn_ptr as _,
|
||||
enabled: true,
|
||||
fast_fn: Some({
|
||||
use deno_core::v8::fast_api::Type;
|
||||
use deno_core::v8::fast_api::CType;
|
||||
deno_core::v8::fast_api::FastFunction::new(
|
||||
&[Type::V8Value, Type::SeqOneByteString],
|
||||
CType::Uint32,
|
||||
Self::v8_fn_ptr_fast as *const ::std::ffi::c_void,
|
||||
)
|
||||
}),
|
||||
is_async: false,
|
||||
is_unstable: false,
|
||||
is_v8: false,
|
||||
arg_count: 1usize as u8,
|
||||
}
|
||||
}
|
||||
fn v8_fn_ptr_fast(
|
||||
_: deno_core::v8::Local<deno_core::v8::Object>,
|
||||
arg0: *mut deno_core::v8::fast_api::FastApiOneByteString,
|
||||
) -> u32 {
|
||||
let mut arg0_temp: [::std::mem::MaybeUninit<u8>; 1024] = [::std::mem::MaybeUninit::uninit(); 1024];
|
||||
let arg0 = &deno_core::_ops::to_str_ptr(unsafe { &mut *arg0 }, &mut arg0_temp);
|
||||
let result = Self::call(arg0);
|
||||
result
|
||||
}
|
||||
extern "C" fn v8_fn_ptr(info: *const deno_core::v8::FunctionCallbackInfo) {
|
||||
let scope = &mut unsafe { deno_core::v8::CallbackScope::new(&*info) };
|
||||
let mut rv = deno_core::v8::ReturnValue::from_function_callback_info(unsafe {
|
||||
&*info
|
||||
});
|
||||
let args = deno_core::v8::FunctionCallbackArguments::from_function_callback_info(unsafe {
|
||||
&*info
|
||||
});
|
||||
let arg0 = args.get(0usize as i32);
|
||||
let mut arg0_temp: [::std::mem::MaybeUninit<u8>; 1024] = [::std::mem::MaybeUninit::uninit(); 1024];
|
||||
let arg0 = &deno_core::_ops::to_str(scope, &arg0, &mut arg0_temp);
|
||||
let result = Self::call(arg0);
|
||||
rv.set_uint32(result as u32);
|
||||
}
|
||||
#[inline(always)]
|
||||
fn call(s: &str) -> u32 {}
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
||||
|
||||
#[op2(fast)]
|
||||
fn op_string_owned(#[string] s: &str) -> u32 {}
|
|
@ -1,49 +0,0 @@
|
|||
#[allow(non_camel_case_types)]
|
||||
pub struct op_string_return {
|
||||
_unconstructable: ::std::marker::PhantomData<()>,
|
||||
}
|
||||
impl deno_core::_ops::Op for op_string_return {
|
||||
const NAME: &'static str = stringify!(op_string_return);
|
||||
const DECL: deno_core::_ops::OpDecl = deno_core::_ops::OpDecl {
|
||||
name: stringify!(op_string_return),
|
||||
v8_fn_ptr: Self::v8_fn_ptr as _,
|
||||
enabled: true,
|
||||
fast_fn: None,
|
||||
is_async: false,
|
||||
is_unstable: false,
|
||||
is_v8: false,
|
||||
arg_count: 0usize as u8,
|
||||
};
|
||||
}
|
||||
impl op_string_return {
|
||||
pub const fn name() -> &'static str {
|
||||
stringify!(op_string_return)
|
||||
}
|
||||
pub const fn decl() -> deno_core::_ops::OpDecl {
|
||||
deno_core::_ops::OpDecl {
|
||||
name: stringify!(op_string_return),
|
||||
v8_fn_ptr: Self::v8_fn_ptr as _,
|
||||
enabled: true,
|
||||
fast_fn: None,
|
||||
is_async: false,
|
||||
is_unstable: false,
|
||||
is_v8: false,
|
||||
arg_count: 0usize as u8,
|
||||
}
|
||||
}
|
||||
extern "C" fn v8_fn_ptr(info: *const deno_core::v8::FunctionCallbackInfo) {
|
||||
let scope = &mut unsafe { deno_core::v8::CallbackScope::new(&*info) };
|
||||
let mut rv = deno_core::v8::ReturnValue::from_function_callback_info(unsafe {
|
||||
&*info
|
||||
});
|
||||
let result = Self::call();
|
||||
if result.is_empty() {
|
||||
rv.set_empty_string();
|
||||
} else {
|
||||
let temp = deno_core::v8::String::new(scope, &result).unwrap();
|
||||
rv.set(temp.into());
|
||||
}
|
||||
}
|
||||
#[inline(always)]
|
||||
pub fn call() -> String {}
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
||||
|
||||
#[op2]
|
||||
#[string]
|
||||
pub fn op_string_return() -> String {}
|
1004
ops/optimizer.rs
1004
ops/optimizer.rs
File diff suppressed because it is too large
Load diff
|
@ -1,11 +0,0 @@
|
|||
=== Optimizer Dump ===
|
||||
returns_result: false
|
||||
has_ref_opstate: false
|
||||
has_rc_opstate: false
|
||||
has_fast_callback_option: false
|
||||
needs_fast_callback_option: false
|
||||
fast_result: Some(Void)
|
||||
fast_parameters: [V8Value, I32]
|
||||
transforms: {}
|
||||
is_async: true
|
||||
fast_compatible: true
|
|
@ -1,137 +0,0 @@
|
|||
#[allow(non_camel_case_types)]
|
||||
///Auto-generated by `deno_ops`, i.e: `#[op]`
|
||||
///
|
||||
///Use `op_void_async::decl()` to get an op-declaration
|
||||
///you can include in a `deno_core::Extension`.
|
||||
pub struct op_void_async {
|
||||
_phantom_data: ::std::marker::PhantomData<()>,
|
||||
}
|
||||
impl deno_core::_ops::Op for op_void_async {
|
||||
const NAME: &'static str = stringify!(op_void_async);
|
||||
const DECL: deno_core::OpDecl = deno_core::OpDecl {
|
||||
name: Self::name(),
|
||||
v8_fn_ptr: Self::v8_fn_ptr as _,
|
||||
enabled: true,
|
||||
fast_fn: {
|
||||
use deno_core::v8::fast_api::CType;
|
||||
use deno_core::v8::fast_api::Type::*;
|
||||
Some(
|
||||
deno_core::v8::fast_api::FastFunction::new(
|
||||
&[V8Value, Int32, CallbackOptions],
|
||||
CType::Void,
|
||||
Self::op_void_async_fast_fn as *const ::std::ffi::c_void,
|
||||
),
|
||||
)
|
||||
},
|
||||
is_async: true,
|
||||
is_unstable: false,
|
||||
is_v8: false,
|
||||
arg_count: 0,
|
||||
};
|
||||
}
|
||||
#[doc(hidden)]
|
||||
impl op_void_async {
|
||||
pub const fn name() -> &'static str {
|
||||
stringify!(op_void_async)
|
||||
}
|
||||
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
||||
pub extern "C" fn v8_fn_ptr(info: *const deno_core::v8::FunctionCallbackInfo) {
|
||||
let info = unsafe { &*info };
|
||||
let scope = &mut unsafe { deno_core::v8::CallbackScope::new(info) };
|
||||
let args = deno_core::v8::FunctionCallbackArguments::from_function_callback_info(
|
||||
info,
|
||||
);
|
||||
let rv = deno_core::v8::ReturnValue::from_function_callback_info(info);
|
||||
Self::v8_func(scope, args, rv);
|
||||
}
|
||||
pub const fn decl() -> deno_core::OpDecl {
|
||||
deno_core::OpDecl {
|
||||
name: Self::name(),
|
||||
v8_fn_ptr: Self::v8_fn_ptr as _,
|
||||
enabled: true,
|
||||
fast_fn: {
|
||||
use deno_core::v8::fast_api::CType;
|
||||
use deno_core::v8::fast_api::Type::*;
|
||||
Some(
|
||||
deno_core::v8::fast_api::FastFunction::new(
|
||||
&[V8Value, Int32, CallbackOptions],
|
||||
CType::Void,
|
||||
Self::op_void_async_fast_fn as *const ::std::ffi::c_void,
|
||||
),
|
||||
)
|
||||
},
|
||||
is_async: true,
|
||||
is_unstable: false,
|
||||
is_v8: false,
|
||||
arg_count: 1usize as u8,
|
||||
}
|
||||
}
|
||||
#[inline]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
#[allow(clippy::extra_unused_lifetimes)]
|
||||
async fn call<'scope>() {}
|
||||
pub fn v8_func<'scope>(
|
||||
scope: &mut deno_core::v8::HandleScope<'scope>,
|
||||
args: deno_core::v8::FunctionCallbackArguments,
|
||||
mut rv: deno_core::v8::ReturnValue,
|
||||
) {
|
||||
use deno_core::futures::FutureExt;
|
||||
let ctx = unsafe {
|
||||
&*(deno_core::v8::Local::<deno_core::v8::External>::cast(args.data()).value()
|
||||
as *const deno_core::_ops::OpCtx)
|
||||
};
|
||||
let promise_id = args.get(0);
|
||||
let promise_id = deno_core::v8::Local::<
|
||||
deno_core::v8::Integer,
|
||||
>::try_from(promise_id)
|
||||
.map(|l| l.value() as deno_core::PromiseId)
|
||||
.map_err(deno_core::anyhow::Error::from);
|
||||
let promise_id: deno_core::PromiseId = match promise_id {
|
||||
Ok(promise_id) => promise_id,
|
||||
Err(err) => {
|
||||
deno_core::_ops::throw_type_error(
|
||||
scope,
|
||||
format!("invalid promise id: {}", err),
|
||||
);
|
||||
return;
|
||||
}
|
||||
};
|
||||
let fut = deno_core::_ops::map_async_op2(ctx, Self::call());
|
||||
let maybe_response = deno_core::_ops::queue_async_op(
|
||||
ctx,
|
||||
scope,
|
||||
false,
|
||||
promise_id,
|
||||
fut,
|
||||
);
|
||||
if let Some(response) = maybe_response {
|
||||
rv.set(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
impl op_void_async {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn op_void_async_fast_fn(
|
||||
_: deno_core::v8::Local<deno_core::v8::Object>,
|
||||
__promise_id: i32,
|
||||
fast_api_callback_options: *mut deno_core::v8::fast_api::FastApiCallbackOptions,
|
||||
) -> () {
|
||||
use deno_core::v8;
|
||||
use deno_core::_ops;
|
||||
let __opts: &mut v8::fast_api::FastApiCallbackOptions = unsafe {
|
||||
&mut *fast_api_callback_options
|
||||
};
|
||||
let __ctx = unsafe {
|
||||
&*(v8::Local::<v8::External>::cast(unsafe { __opts.data.data }).value()
|
||||
as *const _ops::OpCtx)
|
||||
};
|
||||
let op_state = __ctx.state.clone();
|
||||
let result = Self::call();
|
||||
let result = _ops::queue_fast_async_op(
|
||||
__ctx,
|
||||
__promise_id,
|
||||
async move { Ok(result.await) },
|
||||
);
|
||||
result
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue