Skip to content

Commit 238ef6b

Browse files
committed
fix: allow calling synchronous deno functions in the top-level context
1 parent 22e6fd9 commit 238ef6b

File tree

10 files changed

+174
-72
lines changed

10 files changed

+174
-72
lines changed

crates/base/src/runtime/mod.rs

Lines changed: 9 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ use base_rt::get_current_cpu_time_ns;
2323
use base_rt::BlockingScopeCPUUsage;
2424
use base_rt::DenoRuntimeDropToken;
2525
use base_rt::DropToken;
26+
use base_rt::RuntimeState;
2627
use cooked_waker::IntoWaker;
2728
use cooked_waker::WakeRef;
2829
use cpu_timer::CPUTimer;
@@ -51,7 +52,6 @@ use deno_cache::SqliteBackedCache;
5152
use deno_core::error::AnyError;
5253
use deno_core::error::JsError;
5354
use deno_core::serde_json;
54-
use deno_core::unsync::sync::AtomicFlag;
5555
use deno_core::url::Url;
5656
use deno_core::v8;
5757
use deno_core::v8::GCCallbackFlags;
@@ -298,33 +298,6 @@ pub enum WillTerminateReason {
298298
Termination,
299299
}
300300

301-
#[derive(Debug, Clone, Default)]
302-
pub struct RuntimeState {
303-
pub evaluating_mod: Arc<AtomicFlag>,
304-
pub event_loop_completed: Arc<AtomicFlag>,
305-
pub terminated: Arc<AtomicFlag>,
306-
pub found_inspector_session: Arc<AtomicFlag>,
307-
pub mem_reached_half: Arc<AtomicFlag>,
308-
}
309-
310-
impl RuntimeState {
311-
pub fn is_evaluating_mod(&self) -> bool {
312-
self.evaluating_mod.is_raised()
313-
}
314-
315-
pub fn is_event_loop_completed(&self) -> bool {
316-
self.event_loop_completed.is_raised()
317-
}
318-
319-
pub fn is_terminated(&self) -> bool {
320-
self.terminated.is_raised()
321-
}
322-
323-
pub fn is_found_inspector_session(&self) -> bool {
324-
self.found_inspector_session.is_raised()
325-
}
326-
}
327-
328301
#[derive(Debug)]
329302
pub struct RunOptions {
330303
wait_termination_request_token: bool,
@@ -1312,7 +1285,11 @@ where
13121285

13131286
spawn_blocking_non_send(|| {
13141287
let _wall = deno_core::unsync::set_wall().drop_guard();
1288+
let init = scopeguard::guard(self.runtime_state.init.clone(), |v| {
1289+
v.lower();
1290+
});
13151291

1292+
init.raise();
13161293
handle.block_on(
13171294
#[allow(clippy::async_yields_async)]
13181295
async {
@@ -3323,7 +3300,6 @@ mod test {
33233300
impl GetRuntimeContext for Ctx {
33243301
fn get_extra_context() -> impl Serialize {
33253302
serde_json::json!({
3326-
"useReadSyncFileAPI": true,
33273303
"shouldBootstrapMockFnThrowError": true,
33283304
})
33293305
}
@@ -3333,7 +3309,10 @@ mod test {
33333309
"./test_cases/user-worker-san-check",
33343310
None,
33353311
None,
3336-
&["./test_cases/user-worker-san-check/.blocklisted"],
3312+
&[
3313+
"./test_cases/user-worker-san-check/.blocklisted",
3314+
"./test_cases/user-worker-san-check/.whitelisted",
3315+
],
33373316
)
33383317
.set_context::<Ctx>()
33393318
.build()

crates/base/src/worker/supervisor/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use std::time::Duration;
44

55
use anyhow::anyhow;
66
use base_mem_check::MemCheckState;
7+
use base_rt::RuntimeState;
78
use cpu_timer::CPUTimer;
89
use deno_core::v8;
910
use deno_core::InspectorSessionKind;
@@ -31,7 +32,6 @@ use super::pool::SupervisorPolicy;
3132
use super::termination_token::TerminationToken;
3233

3334
use crate::runtime::DenoRuntime;
34-
use crate::runtime::RuntimeState;
3535
use crate::server::ServerFlags;
3636
use crate::utils::units::percentage_value;
3737

crates/base/src/worker/supervisor/strategy_per_worker.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use std::sync::atomic::Ordering;
44
use std::sync::Arc;
55
use std::time::Duration;
66

7+
use base_rt::RuntimeState;
78
use deno_core::unsync::sync::AtomicFlag;
89
use ext_event_worker::events::ShutdownReason;
910
use ext_runtime::PromiseMetrics;
@@ -17,7 +18,6 @@ use tokio::sync::mpsc;
1718
use tokio_util::sync::CancellationToken;
1819
use tracing::Instrument;
1920

20-
use crate::runtime::RuntimeState;
2121
use crate::runtime::WillTerminateReason;
2222
use crate::worker::supervisor::create_wall_clock_beforeunload_alert;
2323
use crate::worker::supervisor::v8_handle_beforeunload;

crates/base/test_cases/user-worker-san-check/.blocklisted

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,18 +23,12 @@ link
2323
linkSync
2424
lstat
2525
lstatSync
26-
makeTempDirSync
2726
makeTempFile
2827
makeTempFileSync
29-
mkdirSync
30-
openSync
31-
readDirSync
3228
readLink
3329
readLinkSync
34-
removeSync
3530
rename
3631
renameSync
37-
statSync
3832
symlink
3933
symlinkSync
4034
truncate
@@ -43,13 +37,11 @@ umask
4337
utime
4438
utimeSync
4539
watchFs
46-
writeFileSync
47-
writeTextFileSync
4840
chdir
4941
dlopen
5042
cron
5143
run
5244
kill
5345
exit
5446
addSignalListener
55-
removeSignalListener
47+
removeSignalListener
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
mkdirSync
2+
readDirSync
3+
removeSync
4+
statSync
5+
writeFileSync
6+
writeTextFileSync
7+
readTextFileSync
8+
readFileSync
9+
makeTempDirSync
Lines changed: 64 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,74 @@
1-
let blocklist: string[] = Deno.readTextFileSync(".blocklisted")
2-
.trim()
3-
.split("\n");
1+
function checkBlocklisted(list: string[]) {
2+
for (const api of list) {
3+
console.log(api);
4+
if (Deno[api] === void 0) {
5+
continue;
6+
}
47

5-
for (const api of blocklist) {
6-
console.log(api);
7-
if (Deno[api] === void 0) {
8-
continue;
9-
}
8+
if (typeof Deno[api] !== "function") {
9+
throw new Error(`invalid api: ${api}`);
10+
}
11+
12+
try {
13+
Deno[api]();
14+
throw new Error(`unreachable: ${api}`);
15+
} catch (ex) {
16+
if (ex instanceof Deno.errors.PermissionDenied) {
17+
continue;
18+
} else if (ex instanceof TypeError) {
19+
if (ex.message === "called MOCK_FN") {
20+
continue;
21+
}
22+
}
23+
}
1024

11-
if (typeof Deno[api] !== "function") {
1225
throw new Error(`invalid api: ${api}`);
1326
}
27+
}
1428

15-
try {
16-
Deno[api]();
17-
throw new Error(`unreachable: ${api}`);
18-
} catch (ex) {
19-
if (ex instanceof Deno.errors.PermissionDenied) {
29+
function checkWhitelisted(list: string[]) {
30+
for (const api of list) {
31+
console.log(api);
32+
if (Deno[api] === void 0) {
2033
continue;
21-
} else if (ex instanceof TypeError) {
22-
if (ex.message === "called MOCK_FN") {
23-
continue;
34+
}
35+
36+
if (typeof Deno[api] !== "function") {
37+
throw new Error(`invalid api: ${api}`);
38+
}
39+
40+
try {
41+
Deno[api]();
42+
throw new Error(`unreachable: ${api}`);
43+
} catch (ex) {
44+
if (ex instanceof Deno.errors.PermissionDenied) {
45+
throw ex;
2446
}
2547
}
2648
}
27-
28-
throw new Error(`invalid api: ${api}`);
2949
}
50+
51+
const blocklist: string[] = Deno.readTextFileSync(".blocklisted")
52+
.trim()
53+
.split("\n");
54+
const whitelisted: string[] = Deno.readTextFileSync(".whitelisted")
55+
.trim()
56+
.split("\n");
57+
58+
checkBlocklisted(blocklist);
59+
checkWhitelisted(whitelisted);
60+
61+
const { promise: fence, resolve, reject } = Promise.withResolvers<void>();
62+
63+
setTimeout(() => {
64+
try {
65+
checkBlocklisted(whitelisted);
66+
resolve();
67+
} catch (ex) {
68+
reject(ex);
69+
}
70+
});
71+
72+
await fence;
73+
74+
export {};

crates/base_rt/src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,12 @@ use tracing::debug;
1818
use tracing::debug_span;
1919
use tracing::Instrument;
2020

21+
mod runtime_state;
22+
2123
pub mod error;
2224

25+
pub use runtime_state::RuntimeState;
26+
2327
pub const DEFAULT_PRIMARY_WORKER_POOL_SIZE: usize = 2;
2428
pub const DEFAULT_USER_WORKER_POOL_SIZE: usize = 1;
2529

crates/base_rt/src/runtime_state.rs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
use std::sync::Arc;
2+
3+
use deno_core::unsync::sync::AtomicFlag;
4+
5+
#[derive(Debug, Clone, Default)]
6+
pub struct RuntimeState {
7+
pub init: Arc<AtomicFlag>,
8+
pub evaluating_mod: Arc<AtomicFlag>,
9+
pub event_loop_completed: Arc<AtomicFlag>,
10+
pub terminated: Arc<AtomicFlag>,
11+
pub found_inspector_session: Arc<AtomicFlag>,
12+
pub mem_reached_half: Arc<AtomicFlag>,
13+
}
14+
15+
impl RuntimeState {
16+
pub fn is_init(&self) -> bool {
17+
self.init.is_raised()
18+
}
19+
20+
pub fn is_evaluating_mod(&self) -> bool {
21+
self.evaluating_mod.is_raised()
22+
}
23+
24+
pub fn is_event_loop_completed(&self) -> bool {
25+
self.event_loop_completed.is_raised()
26+
}
27+
28+
pub fn is_terminated(&self) -> bool {
29+
self.terminated.is_raised()
30+
}
31+
32+
pub fn is_found_inspector_session(&self) -> bool {
33+
self.found_inspector_session.is_raised()
34+
}
35+
}

ext/runtime/js/bootstrap.js

Lines changed: 43 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -399,13 +399,6 @@ const globalProperties = {
399399
};
400400
ObjectDefineProperties(globalThis, globalProperties);
401401

402-
let bootstrapMockFnThrowError = false;
403-
const MOCK_FN = () => {
404-
if (bootstrapMockFnThrowError) {
405-
throw new TypeError("called MOCK_FN");
406-
}
407-
};
408-
409402
const MAKE_HARD_ERR_FN = (msg) => {
410403
return () => {
411404
throw new globalThis_.Deno.errors.PermissionDenied(msg);
@@ -491,6 +484,8 @@ function processRejectionHandled(promise, reason) {
491484
}
492485

493486
globalThis.bootstrapSBEdge = (opts, ctx) => {
487+
let bootstrapMockFnThrowError = false;
488+
494489
globalThis_ = globalThis;
495490

496491
// We should delete this after initialization,
@@ -590,8 +585,8 @@ globalThis.bootstrapSBEdge = (opts, ctx) => {
590585
);
591586
setLanguage("en");
592587

593-
// Find declarative fetch handler
594588
core.addMainModuleHandler((main) => {
589+
// Find declarative fetch handler
595590
if (ObjectHasOwn(main, "default")) {
596591
registerDeclarativeServer(main.default);
597592
}
@@ -669,10 +664,20 @@ globalThis.bootstrapSBEdge = (opts, ctx) => {
669664
"makeTempDir": true,
670665
"readDir": true,
671666

672-
"kill": MOCK_FN,
673-
"exit": MOCK_FN,
674-
"addSignalListener": MOCK_FN,
675-
"removeSignalListener": MOCK_FN,
667+
"kill": "mock",
668+
"exit": "mock",
669+
"addSignalListener": "mock",
670+
"removeSignalListener": "mock",
671+
672+
"statSync": "allowIfRuntimeIsInInit",
673+
"removeSync": "allowIfRuntimeIsInInit",
674+
"writeFileSync": "allowIfRuntimeIsInInit",
675+
"writeTextFileSync": "allowIfRuntimeIsInInit",
676+
"readFileSync": "allowIfRuntimeIsInInit",
677+
"readTextFileSync": "allowIfRuntimeIsInInit",
678+
"mkdirSync": "allowIfRuntimeIsInInit",
679+
"makeTempDirSync": "allowIfRuntimeIsInInit",
680+
"readDirSync": "allowIfRuntimeIsInInit",
676681

677682
// TODO: use a non-hardcoded path
678683
"execPath": () => "/bin/edge-runtime",
@@ -682,6 +687,7 @@ globalThis.bootstrapSBEdge = (opts, ctx) => {
682687
if (ctx?.useReadSyncFileAPI) {
683688
apisToBeOverridden["readFileSync"] = true;
684689
apisToBeOverridden["readTextFileSync"] = true;
690+
apisToBeOverridden["openSync"] = true;
685691
}
686692

687693
const apiNames = ObjectKeys(apisToBeOverridden);
@@ -693,6 +699,31 @@ globalThis.bootstrapSBEdge = (opts, ctx) => {
693699
delete Deno[name];
694700
} else if (typeof value === "function") {
695701
Deno[name] = value;
702+
} else if (typeof value === "string") {
703+
switch (value) {
704+
case "mock": {
705+
Deno[name] = () => {
706+
if (bootstrapMockFnThrowError) {
707+
throw new TypeError("called MOCK_FN");
708+
}
709+
};
710+
break;
711+
}
712+
case "allowIfRuntimeIsInInit": {
713+
const originalFn = Deno[name];
714+
const blocklistedFn = MAKE_HARD_ERR_FN(
715+
`Deno.${name} is blocklisted on the current context`,
716+
);
717+
Deno[name] = (...args) => {
718+
if (ops.op_is_runtime_init()) {
719+
return originalFn(...args);
720+
} else {
721+
return blocklistedFn();
722+
}
723+
};
724+
break;
725+
}
726+
}
696727
}
697728
}
698729
}

0 commit comments

Comments
 (0)