1
0
Fork 0
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:
Matt Mastracci 2023-07-01 18:00:14 -06:00 committed by GitHub
parent b9c0e7cd55
commit e746b6d806
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
223 changed files with 17 additions and 36602 deletions

77
Cargo.lock generated
View file

@ -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"

View file

@ -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"

View file

@ -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();

View file

@ -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);
});

View file

@ -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 };
})();

View file

@ -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);

View file

@ -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);

View file

@ -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

View file

@ -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.

View file

@ -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());
}
}

View file

@ -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);
}
}

View file

@ -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");
}
}

View file

@ -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
_ => "",
}
}

View file

@ -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();
}

View file

@ -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:?}")),
}
}

View file

@ -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)
}

View file

@ -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();
}

View file

@ -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();

View file

@ -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();
}

View file

@ -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();
}

View file

@ -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(())
}

View file

@ -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)
}

View file

@ -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");
}

View file

@ -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();
}

View file

@ -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();
}

View file

@ -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)
),
},)+
]
};
}

View 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());
}
}

View file

@ -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)
}

View file

@ -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>();
}
}

View file

@ -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

File diff suppressed because it is too large Load diff

View file

@ -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,
}
}
}

View file

@ -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,
}
}
}

View file

@ -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;
};
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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()
}
}

File diff suppressed because it is too large Load diff

View file

@ -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

View file

@ -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
}

View file

@ -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
}
}

View file

@ -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
}
}

View file

@ -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())
}

View file

@ -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;
}
}

View file

@ -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)
);
}
}
}

View file

@ -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);
};
}

View file

@ -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;
};

View file

@ -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);
}

View file

@ -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();

View file

@ -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.

View file

@ -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

View file

@ -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;

View file

@ -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>() {}
}

View file

@ -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();

View file

@ -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

View file

@ -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()
}

View file

@ -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 }),
}
}
}
}

View file

@ -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;
}
}

View file

@ -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"

View file

@ -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.
) {
// ...
}
```

View file

@ -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_)
}
}

View file

@ -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)
}
}
}

View file

@ -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

File diff suppressed because it is too large Load diff

View file

@ -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))
}

View file

@ -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;
})
}

View file

@ -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,
}

View file

@ -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."
);
}
}
}

View file

@ -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));
}
}

View file

@ -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
}
}

View file

@ -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
}

View file

@ -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)
}
}

View file

@ -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)
}

View file

@ -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() -> () {}
}

View file

@ -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() -> () {}

View file

@ -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() {}
}

View file

@ -1,4 +0,0 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
#[op2(fast)]
pub fn op_generics<T: Trait>() {}

View file

@ -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> {}
}

View file

@ -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> {}

View file

@ -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> {}
}

View file

@ -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> {}

View file

@ -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 {}
}

View file

@ -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 {}

View file

@ -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 {}
}

View file

@ -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 {}

View file

@ -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> {}
}

View file

@ -1,5 +0,0 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
#[op2]
#[string]
pub fn op_string_return() -> Option<String> {}

View file

@ -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 {}
}

View file

@ -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 {}

View file

@ -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 {}
}

View file

@ -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 {}

View file

@ -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 {}
}

View file

@ -1,5 +0,0 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
#[op2]
#[string]
pub fn op_string_return() -> String {}

File diff suppressed because it is too large Load diff

View file

@ -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

View file

@ -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