deno.land / x / deno@v1.28.2 / core / runtime.rs
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922392339243925392639273928392939303931393239333934393539363937393839393940394139423943394439453946394739483949395039513952395339543955395639573958395939603961396239633964396539663967396839693970397139723973397439753976397739783979398039813982398339843985398639873988398939903991399239933994399539963997399839994000400140024003400440054006400740084009401040114012401340144015401640174018401940204021402240234024402540264027402840294030403140324033403440354036403740384039404040414042404340444045404640474048404940504051405240534054405540564057405840594060406140624063406440654066406740684069407040714072407340744075407640774078407940804081408240834084408540864087408840894090409140924093409440954096// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.
use crate::bindings;use crate::error::generic_error;use crate::error::to_v8_type_error;use crate::error::JsError;use crate::extensions::OpDecl;use crate::extensions::OpEventLoopFn;use crate::inspector::JsRuntimeInspector;use crate::module_specifier::ModuleSpecifier;use crate::modules::ModuleError;use crate::modules::ModuleId;use crate::modules::ModuleLoadId;use crate::modules::ModuleLoader;use crate::modules::ModuleMap;use crate::modules::NoopModuleLoader;use crate::op_void_async;use crate::op_void_sync;use crate::ops::*;use crate::source_map::SourceMapCache;use crate::source_map::SourceMapGetter;use crate::Extension;use crate::OpMiddlewareFn;use crate::OpResult;use crate::OpState;use crate::PromiseId;use anyhow::Error;use futures::channel::oneshot;use futures::future::poll_fn;use futures::future::Future;use futures::future::FutureExt;use futures::stream::FuturesUnordered;use futures::stream::StreamExt;use futures::task::AtomicWaker;use smallvec::SmallVec;use std::any::Any;use std::cell::RefCell;use std::collections::HashMap;use std::collections::HashSet;use std::collections::VecDeque;use std::ffi::c_void;use std::option::Option;use std::rc::Rc;use std::sync::Arc;use std::sync::Mutex;use std::sync::Once;use std::task::Context;use std::task::Poll;use v8::OwnedIsolate;
type PendingOpFuture = OpCall<(PromiseId, OpId, OpResult)>;
pub enum Snapshot { Static(&'static [u8]), JustCreated(v8::StartupData), Boxed(Box<[u8]>),}
pub type JsErrorCreateFn = dyn Fn(JsError) -> Error;
pub type GetErrorClassFn = &'static dyn for<'e> Fn(&'e Error) -> &'static str;
/// Objects that need to live as long as the isolate#[derive(Default)]struct IsolateAllocations { near_heap_limit_callback_data: Option<(Box<RefCell<dyn Any>>, v8::NearHeapLimitCallback)>,}
/// A single execution context of JavaScript. Corresponds roughly to the "Web/// Worker" concept in the DOM. A JsRuntime is a Future that can be used with/// an event loop (Tokio, async_std)./////// The JsRuntime future completes when there is an error or when all/// pending ops have completed.////// Pending ops are created in JavaScript by calling Deno.core.opAsync(), and in Rust/// by implementing an async function that takes a serde::Deserialize "control argument"/// and an optional zero copy buffer, each async Op is tied to a Promise in JavaScript.pub struct JsRuntime { state: Rc<RefCell<JsRuntimeState>>, module_map: Option<Rc<RefCell<ModuleMap>>>, // This is an Option<OwnedIsolate> instead of just OwnedIsolate to workaround // a safety issue with SnapshotCreator. See JsRuntime::drop. v8_isolate: Option<v8::OwnedIsolate>, snapshot_options: SnapshotOptions, built_from_snapshot: bool, allocations: IsolateAllocations, extensions: Vec<Extension>, event_loop_middlewares: Vec<Box<OpEventLoopFn>>,}
pub(crate) struct DynImportModEvaluate { load_id: ModuleLoadId, module_id: ModuleId, promise: v8::Global<v8::Promise>, module: v8::Global<v8::Module>,}
pub(crate) struct ModEvaluate { pub(crate) promise: Option<v8::Global<v8::Promise>>, pub(crate) has_evaluated: bool, pub(crate) handled_promise_rejections: Vec<v8::Global<v8::Promise>>, sender: oneshot::Sender<Result<(), Error>>,}
pub struct CrossIsolateStore<T>(Arc<Mutex<CrossIsolateStoreInner<T>>>);
struct CrossIsolateStoreInner<T> { map: HashMap<u32, T>, last_id: u32,}
impl<T> CrossIsolateStore<T> { pub(crate) fn insert(&self, value: T) -> u32 { let mut store = self.0.lock().unwrap(); let last_id = store.last_id; store.map.insert(last_id, value); store.last_id += 1; last_id } pub(crate) fn take(&self, id: u32) -> Option<T> { let mut store = self.0.lock().unwrap(); store.map.remove(&id) }}
impl<T> Default for CrossIsolateStore<T> { fn default() -> Self { CrossIsolateStore(Arc::new(Mutex::new(CrossIsolateStoreInner { map: Default::default(), last_id: 0, }))) }}
impl<T> Clone for CrossIsolateStore<T> { fn clone(&self) -> Self { Self(self.0.clone()) }}
pub type SharedArrayBufferStore = CrossIsolateStore<v8::SharedRef<v8::BackingStore>>;pub type CompiledWasmModuleStore = CrossIsolateStore<v8::CompiledWasmModule>;
/// Internal state for JsRuntime which is stored in one of v8::Isolate's/// embedder slots.pub struct JsRuntimeState { global_realm: Option<JsRealm>, pub(crate) js_recv_cb: Option<v8::Global<v8::Function>>, pub(crate) js_macrotask_cbs: Vec<v8::Global<v8::Function>>, pub(crate) js_nexttick_cbs: Vec<v8::Global<v8::Function>>, pub(crate) js_promise_reject_cb: Option<v8::Global<v8::Function>>, pub(crate) js_format_exception_cb: Option<v8::Global<v8::Function>>, pub(crate) js_build_custom_error_cb: Option<v8::Global<v8::Function>>, pub(crate) has_tick_scheduled: bool, pub(crate) js_wasm_streaming_cb: Option<v8::Global<v8::Function>>, pub(crate) pending_promise_exceptions: HashMap<v8::Global<v8::Promise>, v8::Global<v8::Value>>, pub(crate) pending_dyn_mod_evaluate: Vec<DynImportModEvaluate>, pub(crate) pending_mod_evaluate: Option<ModEvaluate>, /// A counter used to delay our dynamic import deadlock detection by one spin /// of the event loop. dyn_module_evaluate_idle_counter: u32, pub(crate) source_map_getter: Option<Box<dyn SourceMapGetter>>, pub(crate) source_map_cache: SourceMapCache, pub(crate) pending_ops: FuturesUnordered<PendingOpFuture>, pub(crate) unrefed_ops: HashSet<i32>, pub(crate) have_unpolled_ops: bool, pub(crate) op_state: Rc<RefCell<OpState>>, #[allow(dead_code)] // We don't explicitly re-read this prop but need the slice to live alongside the isolate pub(crate) op_ctxs: Box<[OpCtx]>, pub(crate) shared_array_buffer_store: Option<SharedArrayBufferStore>, pub(crate) compiled_wasm_module_store: Option<CompiledWasmModuleStore>, /// The error that was passed to an `op_dispatch_exception` call. /// It will be retrieved by `exception_to_err_result` and used as an error /// instead of any other exceptions. // TODO(nayeemrmn): This is polled in `exception_to_err_result()` which is // flimsy. Try to poll it similarly to `pending_promise_exceptions`. pub(crate) dispatched_exceptions: VecDeque<v8::Global<v8::Value>>, pub(crate) inspector: Option<Rc<RefCell<JsRuntimeInspector>>>, waker: AtomicWaker,}
fn v8_init( v8_platform: Option<v8::SharedRef<v8::Platform>>, predictable: bool,) { // Include 10MB ICU data file. #[repr(C, align(16))] struct IcuData([u8; 10454784]); static ICU_DATA: IcuData = IcuData(*include_bytes!("icudtl.dat")); v8::icu::set_common_data_71(&ICU_DATA.0).unwrap(); let flags = concat!( " --wasm-test-streaming", " --harmony-import-assertions", " --no-validate-asm", " --turbo_fast_api_calls", " --harmony-change-array-by-copy", ); if predictable { v8::V8::set_flags_from_string(&format!( "{}{}", flags, " --predictable --random-seed=42" )); } else { v8::V8::set_flags_from_string(flags); } let v8_platform = v8_platform .unwrap_or_else(|| v8::new_default_platform(0, false).make_shared()); v8::V8::initialize_platform(v8_platform); v8::V8::initialize();}
pub const V8_WRAPPER_TYPE_INDEX: i32 = 0;pub const V8_WRAPPER_OBJECT_INDEX: i32 = 1;
#[derive(Default)]pub struct RuntimeOptions { /// Source map reference for errors. pub source_map_getter: Option<Box<dyn SourceMapGetter>>, /// Allows to map error type to a string "class" used to represent /// error in JavaScript. pub get_error_class_fn: Option<GetErrorClassFn>, /// Implementation of `ModuleLoader` which will be /// called when V8 requests to load ES modules. /// /// If not provided runtime will error if code being /// executed tries to load modules. pub module_loader: Option<Rc<dyn ModuleLoader>>, /// JsRuntime extensions, not to be confused with ES modules /// these are sets of ops and other JS code to be initialized. pub extensions: Vec<Extension>, /// V8 snapshot that should be loaded on startup. /// /// Currently can't be used with `will_snapshot`. pub startup_snapshot: Option<Snapshot>, /// Prepare runtime to take snapshot of loaded code. /// The snapshot is determinstic and uses predictable random numbers. /// /// Currently can't be used with `startup_snapshot`. pub will_snapshot: bool, /// Isolate creation parameters. pub create_params: Option<v8::CreateParams>, /// V8 platform instance to use. Used when Deno initializes V8 /// (which it only does once), otherwise it's silenty dropped. pub v8_platform: Option<v8::SharedRef<v8::Platform>>, /// The store to use for transferring SharedArrayBuffers between isolates. /// If multiple isolates should have the possibility of sharing /// SharedArrayBuffers, they should use the same [SharedArrayBufferStore]. If /// no [SharedArrayBufferStore] is specified, SharedArrayBuffer can not be /// serialized. pub shared_array_buffer_store: Option<SharedArrayBufferStore>, /// The store to use for transferring `WebAssembly.Module` objects between /// isolates. /// If multiple isolates should have the possibility of sharing /// `WebAssembly.Module` objects, they should use the same /// [CompiledWasmModuleStore]. If no [CompiledWasmModuleStore] is specified, /// `WebAssembly.Module` objects cannot be serialized. pub compiled_wasm_module_store: Option<CompiledWasmModuleStore>, pub inspector: bool,}
#[derive(Copy, Clone, PartialEq, Eq)]pub enum SnapshotOptions { Load, CreateFromExisting, Create, None,}
impl SnapshotOptions { pub fn loaded(&self) -> bool { matches!( self, SnapshotOptions::Load | SnapshotOptions::CreateFromExisting ) } pub fn will_snapshot(&self) -> bool { matches!( self, SnapshotOptions::Create | SnapshotOptions::CreateFromExisting ) } fn from_bools(snapshot_loaded: bool, will_snapshot: bool) -> Self { match (snapshot_loaded, will_snapshot) { (true, true) => SnapshotOptions::CreateFromExisting, (false, true) => SnapshotOptions::Create, (true, false) => SnapshotOptions::Load, (false, false) => SnapshotOptions::None, } }}
impl Drop for JsRuntime { fn drop(&mut self) { if let Some(v8_isolate) = self.v8_isolate.as_mut() { Self::drop_state_and_module_map(v8_isolate); } }}
impl JsRuntime { const STATE_DATA_OFFSET: u32 = 0; const MODULE_MAP_DATA_OFFSET: u32 = 1; /// Only constructor, configuration is done through `options`. pub fn new(mut options: RuntimeOptions) -> Self { let v8_platform = options.v8_platform.take(); static DENO_INIT: Once = Once::new(); DENO_INIT.call_once(move || v8_init(v8_platform, options.will_snapshot)); let has_startup_snapshot = options.startup_snapshot.is_some(); // Add builtins extension options .extensions .insert(0, crate::ops_builtin::init_builtins()); let ops = Self::collect_ops(&mut options.extensions); let mut op_state = OpState::new(ops.len()); if let Some(get_error_class_fn) = options.get_error_class_fn { op_state.get_error_class_fn = get_error_class_fn; } let op_state = Rc::new(RefCell::new(op_state)); let align = std::mem::align_of::<usize>(); let layout = std::alloc::Layout::from_size_align( std::mem::size_of::<*mut v8::OwnedIsolate>(), align, ) .unwrap(); assert!(layout.size() > 0); let isolate_ptr: *mut v8::OwnedIsolate = // SAFETY: we just asserted that layout has non-0 size. unsafe { std::alloc::alloc(layout) as *mut _ }; let state_rc = Rc::new(RefCell::new(JsRuntimeState { pending_promise_exceptions: HashMap::new(), pending_dyn_mod_evaluate: vec![], pending_mod_evaluate: None, dyn_module_evaluate_idle_counter: 0, js_recv_cb: None, js_macrotask_cbs: vec![], js_nexttick_cbs: vec![], js_promise_reject_cb: None, js_format_exception_cb: None, js_build_custom_error_cb: None, has_tick_scheduled: false, js_wasm_streaming_cb: None, source_map_getter: options.source_map_getter, source_map_cache: Default::default(), pending_ops: FuturesUnordered::new(), unrefed_ops: HashSet::new(), shared_array_buffer_store: options.shared_array_buffer_store, compiled_wasm_module_store: options.compiled_wasm_module_store, op_state: op_state.clone(), waker: AtomicWaker::new(), have_unpolled_ops: false, dispatched_exceptions: Default::default(), // Some fields are initialized later after isolate is created inspector: None, op_ctxs: vec![].into_boxed_slice(), global_realm: None, })); let weak = Rc::downgrade(&state_rc); let op_ctxs = ops .into_iter() .enumerate() .map(|(id, decl)| OpCtx { id, state: op_state.clone(), runtime_state: weak.clone(), decl, }) .collect::<Vec<_>>() .into_boxed_slice(); let refs = bindings::external_references(&op_ctxs, !options.will_snapshot); // V8 takes ownership of external_references. let refs: &'static v8::ExternalReferences = Box::leak(Box::new(refs)); let global_context; let (mut isolate, snapshot_options) = if options.will_snapshot { let (snapshot_creator, snapshot_loaded) = if let Some(snapshot) = options.startup_snapshot { ( match snapshot { Snapshot::Static(data) => { v8::Isolate::snapshot_creator_from_existing_snapshot( data, Some(refs), ) } Snapshot::JustCreated(data) => { v8::Isolate::snapshot_creator_from_existing_snapshot( data, Some(refs), ) } Snapshot::Boxed(data) => { v8::Isolate::snapshot_creator_from_existing_snapshot( data, Some(refs), ) } }, true, ) } else { (v8::Isolate::snapshot_creator(Some(refs)), false) }; let snapshot_options = SnapshotOptions::from_bools(snapshot_loaded, options.will_snapshot); let mut isolate = JsRuntime::setup_isolate(snapshot_creator); { // SAFETY: this is first use of `isolate_ptr` so we are sure we're // not overwriting an existing pointer. isolate = unsafe { isolate_ptr.write(isolate); isolate_ptr.read() }; let scope = &mut v8::HandleScope::new(&mut isolate); let context = bindings::initialize_context(scope, &op_ctxs, snapshot_options); global_context = v8::Global::new(scope, context); scope.set_default_context(context); } (isolate, snapshot_options) } else { let mut params = options .create_params .take() .unwrap_or_else(|| { v8::CreateParams::default().embedder_wrapper_type_info_offsets( V8_WRAPPER_TYPE_INDEX, V8_WRAPPER_OBJECT_INDEX, ) }) .external_references(&**refs); let snapshot_loaded = if let Some(snapshot) = options.startup_snapshot { params = match snapshot { Snapshot::Static(data) => params.snapshot_blob(data), Snapshot::JustCreated(data) => params.snapshot_blob(data), Snapshot::Boxed(data) => params.snapshot_blob(data), }; true } else { false }; let snapshot_options = SnapshotOptions::from_bools(snapshot_loaded, options.will_snapshot); let isolate = v8::Isolate::new(params); let mut isolate = JsRuntime::setup_isolate(isolate); { // SAFETY: this is first use of `isolate_ptr` so we are sure we're // not overwriting an existing pointer. isolate = unsafe { isolate_ptr.write(isolate); isolate_ptr.read() }; let scope = &mut v8::HandleScope::new(&mut isolate); let context = bindings::initialize_context(scope, &op_ctxs, snapshot_options); global_context = v8::Global::new(scope, context); } (isolate, snapshot_options) }; op_state.borrow_mut().put(isolate_ptr); let inspector = if options.inspector { Some(JsRuntimeInspector::new( &mut isolate, global_context.clone(), )) } else { None }; let loader = options .module_loader .unwrap_or_else(|| Rc::new(NoopModuleLoader)); { let mut state = state_rc.borrow_mut(); state.global_realm = Some(JsRealm(global_context)); state.op_ctxs = op_ctxs; state.inspector = inspector; } isolate.set_data( Self::STATE_DATA_OFFSET, Rc::into_raw(state_rc.clone()) as *mut c_void, ); let module_map_rc = Rc::new(RefCell::new(ModuleMap::new(loader, op_state))); isolate.set_data( Self::MODULE_MAP_DATA_OFFSET, Rc::into_raw(module_map_rc.clone()) as *mut c_void, ); let mut js_runtime = Self { v8_isolate: Some(isolate), built_from_snapshot: has_startup_snapshot, snapshot_options, allocations: IsolateAllocations::default(), event_loop_middlewares: Vec::with_capacity(options.extensions.len()), extensions: options.extensions, state: state_rc, module_map: Some(module_map_rc), }; // Init resources and ops before extensions to make sure they are // available during the initialization process. js_runtime.init_extension_ops().unwrap(); // TODO(@AaronO): diff extensions inited in snapshot and those provided // for now we assume that snapshot and extensions always match if !has_startup_snapshot { let realm = js_runtime.global_realm(); js_runtime.init_extension_js(&realm).unwrap(); } // Init callbacks (opresolve) js_runtime.init_cbs(); js_runtime } fn drop_state_and_module_map(v8_isolate: &mut OwnedIsolate) { let state_ptr = v8_isolate.get_data(Self::STATE_DATA_OFFSET); let state_rc = // SAFETY: We are sure that it's a valid pointer for whole lifetime of // the runtime. unsafe { Rc::from_raw(state_ptr as *const RefCell<JsRuntimeState>) }; drop(state_rc); let module_map_ptr = v8_isolate.get_data(Self::MODULE_MAP_DATA_OFFSET); let module_map_rc = // SAFETY: We are sure that it's a valid pointer for whole lifetime of // the runtime. unsafe { Rc::from_raw(module_map_ptr as *const RefCell<ModuleMap>) }; drop(module_map_rc); } #[inline] fn get_module_map(&mut self) -> &Rc<RefCell<ModuleMap>> { self.module_map.as_ref().unwrap() } #[inline] pub fn global_context(&mut self) -> v8::Global<v8::Context> { self.global_realm().0 } #[inline] pub fn v8_isolate(&mut self) -> &mut v8::OwnedIsolate { self.v8_isolate.as_mut().unwrap() } #[inline] pub fn inspector(&mut self) -> Rc<RefCell<JsRuntimeInspector>> { self.state.borrow().inspector() } #[inline] pub fn global_realm(&mut self) -> JsRealm { let state = self.state.borrow(); state.global_realm.clone().unwrap() } /// Creates a new realm (V8 context) in this JS execution context, /// pre-initialized with all of the extensions that were passed in /// [`RuntimeOptions::extensions`] when the [`JsRuntime`] was constructed. /// /// If the [`JsRuntime`] was not built from a snapshot (see /// [`RuntimeOptions::startup_snapshot`]), the JS code for the extensions will /// be run in the call to this method. In contrast, if there is a snapshot, /// that will be used instead, and the extensions' initialization will come /// "for free". pub fn create_realm(&mut self) -> Result<JsRealm, Error> { let realm = { // SAFETY: Having the scope tied to self's lifetime makes it impossible to // reference self.ops while the scope is alive. Here we turn it into an // unbound lifetime, which is sound because 1. it only lives until the end // of this block, and 2. the HandleScope only has access to the isolate, // and nothing else we're accessing from self does. let scope = &mut v8::HandleScope::new(unsafe { &mut *(self.v8_isolate() as *mut v8::OwnedIsolate) }); let context = bindings::initialize_context( scope, &self.state.borrow().op_ctxs, SnapshotOptions::from_bools( self.built_from_snapshot, self.snapshot_options.will_snapshot(), ), ); JsRealm::new(v8::Global::new(scope, context)) }; if !self.built_from_snapshot { self.init_extension_js(&realm)?; } Ok(realm) } #[inline] pub fn handle_scope(&mut self) -> v8::HandleScope { self.global_realm().handle_scope(self.v8_isolate()) } fn setup_isolate(mut isolate: v8::OwnedIsolate) -> v8::OwnedIsolate { isolate.set_capture_stack_trace_for_uncaught_exceptions(true, 10); isolate.set_promise_reject_callback(bindings::promise_reject_callback); isolate.set_host_initialize_import_meta_object_callback( bindings::host_initialize_import_meta_object_callback, ); isolate.set_host_import_module_dynamically_callback( bindings::host_import_module_dynamically_callback, ); isolate.set_wasm_async_resolve_promise_callback( bindings::wasm_async_resolve_promise_callback, ); isolate } pub(crate) fn state(isolate: &v8::Isolate) -> Rc<RefCell<JsRuntimeState>> { let state_ptr = isolate.get_data(Self::STATE_DATA_OFFSET); let state_rc = // SAFETY: We are sure that it's a valid pointer for whole lifetime of // the runtime. unsafe { Rc::from_raw(state_ptr as *const RefCell<JsRuntimeState>) }; let state = state_rc.clone(); Rc::into_raw(state_rc); state } pub(crate) fn module_map(isolate: &v8::Isolate) -> Rc<RefCell<ModuleMap>> { let module_map_ptr = isolate.get_data(Self::MODULE_MAP_DATA_OFFSET); let module_map_rc = // SAFETY: We are sure that it's a valid pointer for whole lifetime of // the runtime. unsafe { Rc::from_raw(module_map_ptr as *const RefCell<ModuleMap>) }; let module_map = module_map_rc.clone(); Rc::into_raw(module_map_rc); module_map } /// Initializes JS of provided Extensions in the given realm fn init_extension_js(&mut self, realm: &JsRealm) -> Result<(), Error> { // Take extensions to avoid double-borrow let mut extensions: Vec<Extension> = std::mem::take(&mut self.extensions); for m in extensions.iter_mut() { let js_files = m.init_js(); for (filename, source) in js_files { // TODO(@AaronO): use JsRuntime::execute_static() here to move src off heap realm.execute_script(self.v8_isolate(), filename, source)?; } } // Restore extensions self.extensions = extensions; Ok(()) } /// Collects ops from extensions & applies middleware fn collect_ops(extensions: &mut [Extension]) -> Vec<OpDecl> { // Middleware let middleware: Vec<Box<OpMiddlewareFn>> = extensions .iter_mut() .filter_map(|e| e.init_middleware()) .collect(); // macroware wraps an opfn in all the middleware let macroware = move |d| middleware.iter().fold(d, |d, m| m(d)); // Flatten ops, apply middlware & override disabled ops extensions .iter_mut() .filter_map(|e| e.init_ops()) .flatten() .map(|d| OpDecl { name: d.name, ..macroware(d) }) .map(|op| match op.enabled { true => op, false => OpDecl { v8_fn_ptr: match op.is_async { true => op_void_async::v8_fn_ptr(), false => op_void_sync::v8_fn_ptr(), }, ..op }, }) .collect() } /// Initializes ops of provided Extensions fn init_extension_ops(&mut self) -> Result<(), Error> { let op_state = self.op_state(); // Take extensions to avoid double-borrow let mut extensions: Vec<Extension> = std::mem::take(&mut self.extensions); // Setup state for e in extensions.iter_mut() { // ops are already registered during in bindings::initialize_context(); e.init_state(&mut op_state.borrow_mut())?; // Setup event-loop middleware if let Some(middleware) = e.init_event_loop_middleware() { self.event_loop_middlewares.push(middleware); } } // Restore extensions self.extensions = extensions; Ok(()) } pub fn eval<'s, T>( scope: &mut v8::HandleScope<'s>, code: &str, ) -> Option<v8::Local<'s, T>> where v8::Local<'s, T>: TryFrom<v8::Local<'s, v8::Value>, Error = v8::DataError>, { let scope = &mut v8::EscapableHandleScope::new(scope); let source = v8::String::new(scope, code).unwrap(); let script = v8::Script::compile(scope, source, None).unwrap(); let v = script.run(scope)?; scope.escape(v).try_into().ok() } /// Grabs a reference to core.js' opresolve & syncOpsCache() fn init_cbs(&mut self) { let scope = &mut self.handle_scope(); let recv_cb = Self::eval::<v8::Function>(scope, "Deno.core.opresolve").unwrap(); let recv_cb = v8::Global::new(scope, recv_cb); let build_custom_error_cb = Self::eval::<v8::Function>(scope, "Deno.core.buildCustomError") .expect("Deno.core.buildCustomError is undefined in the realm"); let build_custom_error_cb = v8::Global::new(scope, build_custom_error_cb); // Put global handles in state let state_rc = JsRuntime::state(scope); let mut state = state_rc.borrow_mut(); state.js_recv_cb.replace(recv_cb); state .js_build_custom_error_cb .replace(build_custom_error_cb); } /// Returns the runtime's op state, which can be used to maintain ops /// and access resources between op calls. pub fn op_state(&mut self) -> Rc<RefCell<OpState>> { let state = self.state.borrow(); state.op_state.clone() } /// Executes traditional JavaScript code (traditional = not ES modules). /// /// The execution takes place on the current global context, so it is possible /// to maintain local JS state and invoke this method multiple times. /// /// `name` can be a filepath or any other string, eg. /// /// - "/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( &mut self, name: &str, source_code: &str, ) -> Result<v8::Global<v8::Value>, Error> { self .global_realm() .execute_script(self.v8_isolate(), name, source_code) } /// Takes a snapshot. The isolate should have been created with will_snapshot /// set to true. /// /// `Error` can usually be downcast to `JsError`. pub fn snapshot(mut self) -> v8::StartupData { // Nuke Deno.core.ops.* to avoid ExternalReference snapshotting issues // TODO(@AaronO): make ops stable across snapshots { let scope = &mut self.handle_scope(); let o = Self::eval::<v8::Object>(scope, "Deno.core.ops").unwrap(); let names = o.get_own_property_names(scope, Default::default()).unwrap(); for i in 0..names.length() { let key = names.get_index(scope, i).unwrap(); o.delete(scope, key); } } self.state.borrow_mut().global_realm.take(); self.state.borrow_mut().inspector.take(); // Drop existing ModuleMap to drop v8::Global handles { self.module_map.take(); let v8_isolate = self.v8_isolate(); Self::drop_state_and_module_map(v8_isolate); } // Drop other v8::Global handles before snapshotting { let mut state = self.state.borrow_mut(); std::mem::take(&mut state.js_recv_cb); std::mem::take(&mut state.js_promise_reject_cb); std::mem::take(&mut state.js_format_exception_cb); std::mem::take(&mut state.js_wasm_streaming_cb); std::mem::take(&mut state.js_build_custom_error_cb); state.js_macrotask_cbs.clear(); state.js_nexttick_cbs.clear(); } let snapshot_creator = self.v8_isolate.take().unwrap(); snapshot_creator .create_blob(v8::FunctionCodeHandling::Keep) .unwrap() } /// Returns the namespace object of a module. /// /// This is only available after module evaluation has completed. /// This function panics if module has not been instantiated. pub fn get_module_namespace( &mut self, module_id: ModuleId, ) -> Result<v8::Global<v8::Object>, Error> { let module_map_rc = Self::module_map(self.v8_isolate()); let module_handle = module_map_rc .borrow() .get_handle(module_id) .expect("ModuleInfo not found"); let scope = &mut self.handle_scope(); let module = module_handle.open(scope); if module.get_status() == v8::ModuleStatus::Errored { let exception = module.get_exception(); return exception_to_err_result(scope, exception, false); } assert!(matches!( module.get_status(), v8::ModuleStatus::Instantiated | v8::ModuleStatus::Evaluated )); let module_namespace: v8::Local<v8::Object> = v8::Local::try_from(module.get_module_namespace()) .map_err(|err: v8::DataError| generic_error(err.to_string()))?; Ok(v8::Global::new(scope, module_namespace)) } /// Registers a callback on the isolate when the memory limits are approached. /// Use this to prevent V8 from crashing the process when reaching the limit. /// /// Calls the closure with the current heap limit and the initial heap limit. /// The return value of the closure is set as the new limit. pub fn add_near_heap_limit_callback<C>(&mut self, cb: C) where C: FnMut(usize, usize) -> usize + 'static, { let boxed_cb = Box::new(RefCell::new(cb)); let data = boxed_cb.as_ptr() as *mut c_void; let prev = self .allocations .near_heap_limit_callback_data .replace((boxed_cb, near_heap_limit_callback::<C>)); if let Some((_, prev_cb)) = prev { self .v8_isolate() .remove_near_heap_limit_callback(prev_cb, 0); } self .v8_isolate() .add_near_heap_limit_callback(near_heap_limit_callback::<C>, data); } pub fn remove_near_heap_limit_callback(&mut self, heap_limit: usize) { if let Some((_, cb)) = self.allocations.near_heap_limit_callback_data.take() { self .v8_isolate() .remove_near_heap_limit_callback(cb, heap_limit); } } fn pump_v8_message_loop(&mut self) -> Result<(), Error> { let scope = &mut self.handle_scope(); while v8::Platform::pump_message_loop( &v8::V8::get_current_platform(), scope, false, // don't block if there are no tasks ) { // do nothing } let tc_scope = &mut v8::TryCatch::new(scope); tc_scope.perform_microtask_checkpoint(); match tc_scope.exception() { None => Ok(()), Some(exception) => exception_to_err_result(tc_scope, exception, false), } } pub fn maybe_init_inspector(&mut self) { if self.state.borrow().inspector.is_some() { return; } let mut state = self.state.borrow_mut(); state.inspector = Some(JsRuntimeInspector::new( self.v8_isolate.as_mut().unwrap(), state.global_realm.clone().unwrap().0, )); } pub fn poll_value( &mut self, global: &v8::Global<v8::Value>, cx: &mut Context, ) -> Poll<Result<v8::Global<v8::Value>, Error>> { let state = self.poll_event_loop(cx, false); let mut scope = self.handle_scope(); let local = v8::Local::<v8::Value>::new(&mut scope, global); if let Ok(promise) = v8::Local::<v8::Promise>::try_from(local) { match promise.state() { v8::PromiseState::Pending => match state { Poll::Ready(Ok(_)) => { let msg = "Promise resolution is still pending but the event loop has already resolved."; Poll::Ready(Err(generic_error(msg))) } Poll::Ready(Err(e)) => Poll::Ready(Err(e)), Poll::Pending => Poll::Pending, }, v8::PromiseState::Fulfilled => { let value = promise.result(&mut scope); let value_handle = v8::Global::new(&mut scope, value); Poll::Ready(Ok(value_handle)) } v8::PromiseState::Rejected => { let exception = promise.result(&mut scope); Poll::Ready(exception_to_err_result(&mut scope, exception, false)) } } } else { let value_handle = v8::Global::new(&mut scope, local); Poll::Ready(Ok(value_handle)) } } /// Waits for the given value to resolve while polling the event loop. /// /// This future resolves when either the value is resolved or the event loop runs to /// completion. pub async fn resolve_value( &mut self, global: v8::Global<v8::Value>, ) -> Result<v8::Global<v8::Value>, Error> { poll_fn(|cx| self.poll_value(&global, cx)).await } /// Runs event loop to completion /// /// This future resolves when: /// - there are no more pending dynamic imports /// - there are no more pending ops /// - there are no more active inspector sessions (only if `wait_for_inspector` is set to true) pub async fn run_event_loop( &mut self, wait_for_inspector: bool, ) -> Result<(), Error> { poll_fn(|cx| self.poll_event_loop(cx, wait_for_inspector)).await } /// Runs a single tick of event loop /// /// If `wait_for_inspector` is set to true event loop /// will return `Poll::Pending` if there are active inspector sessions. pub fn poll_event_loop( &mut self, cx: &mut Context, wait_for_inspector: bool, ) -> Poll<Result<(), Error>> { let has_inspector: bool; { let state = self.state.borrow(); has_inspector = state.inspector.is_some(); state.waker.register(cx.waker()); } if has_inspector { // We poll the inspector first. let _ = self.inspector().borrow_mut().poll_unpin(cx); } self.pump_v8_message_loop()?; // Ops { self.resolve_async_ops(cx)?; self.drain_nexttick()?; self.drain_macrotasks()?; self.check_promise_exceptions()?; } // Dynamic module loading - ie. modules loaded using "import()" { // Run in a loop so that dynamic imports that only depend on another // dynamic import can be resolved in this event loop iteration. // // For example, a dynamically imported module like the following can be // immediately resolved after `dependency.ts` is fully evaluated, but it // wouldn't if not for this loop. // // await delay(1000); // await import("./dependency.ts"); // console.log("test") // loop { let poll_imports = self.prepare_dyn_imports(cx)?; assert!(poll_imports.is_ready()); let poll_imports = self.poll_dyn_imports(cx)?; assert!(poll_imports.is_ready()); if !self.evaluate_dyn_imports() { break; } } self.check_promise_exceptions()?; } // Event loop middlewares let mut maybe_scheduling = false; { let op_state = self.state.borrow().op_state.clone(); for f in &self.event_loop_middlewares { if f(op_state.clone(), cx) { maybe_scheduling = true; } } } // Top level module self.evaluate_pending_module(); let pending_state = self.event_loop_pending_state(); if !pending_state.is_pending() && !maybe_scheduling { if has_inspector { let inspector_has_active_sessions = self.inspector().borrow_mut().has_active_sessions(); if wait_for_inspector && inspector_has_active_sessions { return Poll::Pending; } } return Poll::Ready(Ok(())); } let mut state = self.state.borrow_mut(); // Check if more async ops have been dispatched // during this turn of event loop. // If there are any pending background tasks, we also wake the runtime to // make sure we don't miss them. // TODO(andreubotella) The event loop will spin as long as there are pending // background tasks. We should look into having V8 notify us when a // background task is done. if state.have_unpolled_ops || pending_state.has_pending_background_tasks || pending_state.has_tick_scheduled || maybe_scheduling { state.waker.wake(); } if pending_state.has_pending_module_evaluation { if pending_state.has_pending_refed_ops || pending_state.has_pending_dyn_imports || pending_state.has_pending_dyn_module_evaluation || pending_state.has_pending_background_tasks || pending_state.has_tick_scheduled || maybe_scheduling { // pass, will be polled again } else { let msg = "Module evaluation is still pending but there are no pending ops or dynamic imports. This situation is often caused by unresolved promises."; return Poll::Ready(Err(generic_error(msg))); } } if pending_state.has_pending_dyn_module_evaluation { if pending_state.has_pending_refed_ops || pending_state.has_pending_dyn_imports || pending_state.has_pending_background_tasks || pending_state.has_tick_scheduled { // pass, will be polled again } else if state.dyn_module_evaluate_idle_counter >= 1 { let mut msg = "Dynamically imported module evaluation is still pending but there are no pending ops. This situation is often caused by unresolved promises.Pending dynamic modules:\n".to_string(); let module_map = self.module_map.as_mut().unwrap().borrow_mut(); for pending_evaluate in &state.pending_dyn_mod_evaluate { let module_info = module_map .get_info_by_id(&pending_evaluate.module_id) .unwrap(); msg.push_str("- "); msg.push_str(module_info.name.as_str()); } return Poll::Ready(Err(generic_error(msg))); } else { // Delay the above error by one spin of the event loop. A dynamic import // evaluation may complete during this, in which case the counter will // reset. state.dyn_module_evaluate_idle_counter += 1; state.waker.wake(); } } Poll::Pending } fn event_loop_pending_state(&mut self) -> EventLoopPendingState { let isolate = self.v8_isolate.as_mut().unwrap(); let state = self.state.borrow_mut(); let module_map = self.module_map.as_mut().unwrap().borrow(); EventLoopPendingState { has_pending_refed_ops: state.pending_ops.len() > state.unrefed_ops.len(), has_pending_dyn_imports: module_map.has_pending_dynamic_imports(), has_pending_dyn_module_evaluation: !state .pending_dyn_mod_evaluate .is_empty(), has_pending_module_evaluation: state.pending_mod_evaluate.is_some(), has_pending_background_tasks: isolate.has_pending_background_tasks(), has_tick_scheduled: state.has_tick_scheduled, } } pub(crate) fn event_loop_pending_state_from_isolate( isolate: &mut v8::Isolate, ) -> EventLoopPendingState { let state_rc = Self::state(isolate); let module_map_rc = Self::module_map(isolate); let state = state_rc.borrow_mut(); let module_map = module_map_rc.borrow(); EventLoopPendingState { has_pending_refed_ops: state.pending_ops.len() > state.unrefed_ops.len(), has_pending_dyn_imports: module_map.has_pending_dynamic_imports(), has_pending_dyn_module_evaluation: !state .pending_dyn_mod_evaluate .is_empty(), has_pending_module_evaluation: state.pending_mod_evaluate.is_some(), has_pending_background_tasks: isolate.has_pending_background_tasks(), has_tick_scheduled: state.has_tick_scheduled, } }}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]pub(crate) struct EventLoopPendingState { has_pending_refed_ops: bool, has_pending_dyn_imports: bool, has_pending_dyn_module_evaluation: bool, has_pending_module_evaluation: bool, has_pending_background_tasks: bool, has_tick_scheduled: bool,}impl EventLoopPendingState { pub fn is_pending(&self) -> bool { self.has_pending_refed_ops || self.has_pending_dyn_imports || self.has_pending_dyn_module_evaluation || self.has_pending_module_evaluation || self.has_pending_background_tasks || self.has_tick_scheduled }}
extern "C" fn near_heap_limit_callback<F>( data: *mut c_void, current_heap_limit: usize, initial_heap_limit: usize,) -> usizewhere F: FnMut(usize, usize) -> usize,{ // SAFETY: The data is a pointer to the Rust callback function. It is stored // in `JsRuntime::allocations` and thus is guaranteed to outlive the isolate. let callback = unsafe { &mut *(data as *mut F) }; callback(current_heap_limit, initial_heap_limit)}
impl JsRuntimeState { pub(crate) fn inspector(&self) -> Rc<RefCell<JsRuntimeInspector>> { self.inspector.as_ref().unwrap().clone() } /// Called by `bindings::host_import_module_dynamically_callback` /// after initiating new dynamic import load. pub fn notify_new_dynamic_import(&mut self) { // Notify event loop to poll again soon. self.waker.wake(); }}
pub(crate) fn exception_to_err_result<'s, T>( scope: &mut v8::HandleScope<'s>, exception: v8::Local<v8::Value>, in_promise: bool,) -> Result<T, Error> { let state_rc = JsRuntime::state(scope); let was_terminating_execution = scope.is_execution_terminating(); // 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 = state .dispatched_exceptions .back() .map(|exception| v8::Local::new(scope, exception.clone())) .unwrap_or_else(|| { // Maybe make a new exception object. 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(); } Err(js_error.into())}
// Related to module loadingimpl JsRuntime { pub(crate) fn instantiate_module( &mut self, id: ModuleId, ) -> Result<(), v8::Global<v8::Value>> { let module_map_rc = Self::module_map(self.v8_isolate()); let scope = &mut self.handle_scope(); let tc_scope = &mut v8::TryCatch::new(scope); let module = module_map_rc .borrow() .get_handle(id) .map(|handle| v8::Local::new(tc_scope, handle)) .expect("ModuleInfo not found"); if module.get_status() == v8::ModuleStatus::Errored { return Err(v8::Global::new(tc_scope, module.get_exception())); } // IMPORTANT: No borrows to `ModuleMap` can be held at this point because // `module_resolve_callback` will be calling into `ModuleMap` from within // the isolate. let instantiate_result = module.instantiate_module(tc_scope, bindings::module_resolve_callback); if instantiate_result.is_none() { let exception = tc_scope.exception().unwrap(); return Err(v8::Global::new(tc_scope, exception)); } Ok(()) } fn dynamic_import_module_evaluate( &mut self, load_id: ModuleLoadId, id: ModuleId, ) -> Result<(), Error> { let module_map_rc = Self::module_map(self.v8_isolate()); let module_handle = module_map_rc .borrow() .get_handle(id) .expect("ModuleInfo not found"); let status = { let scope = &mut self.handle_scope(); let module = module_handle.open(scope); module.get_status() }; match status { v8::ModuleStatus::Instantiated | v8::ModuleStatus::Evaluated => {} _ => return Ok(()), } // IMPORTANT: Top-level-await is enabled, which means that return value // of module evaluation is a promise. // // This promise is internal, and not the same one that gets returned to // the user. We add an empty `.catch()` handler so that it does not result // in an exception if it rejects. That will instead happen for the other // promise if not handled by the user. // // For more details see: // https://github.com/denoland/deno/issues/4908 // https://v8.dev/features/top-level-await#module-execution-order let global_realm = self.state.borrow_mut().global_realm.clone().unwrap(); let scope = &mut global_realm.handle_scope(self.v8_isolate.as_mut().unwrap()); let tc_scope = &mut v8::TryCatch::new(scope); let module = v8::Local::new(tc_scope, &module_handle); let maybe_value = module.evaluate(tc_scope); // Update status after evaluating. let status = module.get_status(); if let Some(value) = maybe_value { assert!( status == v8::ModuleStatus::Evaluated || status == v8::ModuleStatus::Errored ); let promise = v8::Local::<v8::Promise>::try_from(value) .expect("Expected to get promise as module evaluation result"); let empty_fn = bindings::create_empty_fn(tc_scope).unwrap(); promise.catch(tc_scope, empty_fn); let promise_global = v8::Global::new(tc_scope, promise); let module_global = v8::Global::new(tc_scope, module); let dyn_import_mod_evaluate = DynImportModEvaluate { load_id, module_id: id, promise: promise_global, module: module_global, }; self .state .borrow_mut() .pending_dyn_mod_evaluate .push(dyn_import_mod_evaluate); } else if tc_scope.has_terminated() || tc_scope.is_execution_terminating() { return Err( generic_error("Cannot evaluate dynamically imported module, because JavaScript execution has been terminated.") ); } else { assert!(status == v8::ModuleStatus::Errored); } Ok(()) } // TODO(bartlomieju): make it return `ModuleEvaluationFuture`? /// Evaluates an already instantiated ES module. /// /// Returns a receiver handle that resolves when module promise resolves. /// Implementors must manually call [`JsRuntime::run_event_loop`] to drive /// module evaluation future. /// /// `Error` can usually be downcast to `JsError` and should be awaited and /// checked after [`JsRuntime::run_event_loop`] completion. /// /// This function panics if module has not been instantiated. pub fn mod_evaluate( &mut self, id: ModuleId, ) -> oneshot::Receiver<Result<(), Error>> { let state_rc = self.state.clone(); let module_map_rc = Self::module_map(self.v8_isolate()); let scope = &mut self.handle_scope(); let tc_scope = &mut v8::TryCatch::new(scope); let module = module_map_rc .borrow() .get_handle(id) .map(|handle| v8::Local::new(tc_scope, handle)) .expect("ModuleInfo not found"); let mut status = module.get_status(); assert_eq!(status, v8::ModuleStatus::Instantiated); let (sender, receiver) = oneshot::channel(); // IMPORTANT: Top-level-await is enabled, which means that return value // of module evaluation is a promise. // // Because that promise is created internally by V8, when error occurs during // module evaluation the promise is rejected, and since the promise has no rejection // handler it will result in call to `bindings::promise_reject_callback` adding // the promise to pending promise rejection table - meaning JsRuntime will return // error on next poll(). // // This situation is not desirable as we want to manually return error at the // end of this function to handle it further. It means we need to manually // remove this promise from pending promise rejection table. // // For more details see: // https://github.com/denoland/deno/issues/4908 // https://v8.dev/features/top-level-await#module-execution-order { let mut state = state_rc.borrow_mut(); assert!( state.pending_mod_evaluate.is_none(), "There is already pending top level module evaluation" ); state.pending_mod_evaluate = Some(ModEvaluate { promise: None, has_evaluated: false, handled_promise_rejections: vec![], sender, }); } let maybe_value = module.evaluate(tc_scope); { let mut state = state_rc.borrow_mut(); let pending_mod_evaluate = state.pending_mod_evaluate.as_mut().unwrap(); pending_mod_evaluate.has_evaluated = true; } // Update status after evaluating. status = module.get_status(); let has_dispatched_exception = !state_rc.borrow_mut().dispatched_exceptions.is_empty(); if has_dispatched_exception { // This will be overrided in `exception_to_err_result()`. let exception = v8::undefined(tc_scope).into(); let pending_mod_evaluate = { let mut state = state_rc.borrow_mut(); state.pending_mod_evaluate.take().unwrap() }; pending_mod_evaluate .sender .send(exception_to_err_result(tc_scope, exception, false)) .expect("Failed to send module evaluation error."); } else if let Some(value) = maybe_value { assert!( status == v8::ModuleStatus::Evaluated || status == v8::ModuleStatus::Errored ); let promise = v8::Local::<v8::Promise>::try_from(value) .expect("Expected to get promise as module evaluation result"); let promise_global = v8::Global::new(tc_scope, promise); let mut state = state_rc.borrow_mut(); { let pending_mod_evaluate = state.pending_mod_evaluate.as_ref().unwrap(); let pending_rejection_was_already_handled = pending_mod_evaluate .handled_promise_rejections .contains(&promise_global); if !pending_rejection_was_already_handled { state.pending_promise_exceptions.remove(&promise_global); } } let promise_global = v8::Global::new(tc_scope, promise); state.pending_mod_evaluate.as_mut().unwrap().promise = Some(promise_global); tc_scope.perform_microtask_checkpoint(); } else if tc_scope.has_terminated() || tc_scope.is_execution_terminating() { let pending_mod_evaluate = { let mut state = state_rc.borrow_mut(); state.pending_mod_evaluate.take().unwrap() }; pending_mod_evaluate.sender.send(Err( generic_error("Cannot evaluate module, because JavaScript execution has been terminated.") )).expect("Failed to send module evaluation error."); } else { assert!(status == v8::ModuleStatus::Errored); } receiver } fn dynamic_import_reject( &mut self, id: ModuleLoadId, exception: v8::Global<v8::Value>, ) { let module_map_rc = Self::module_map(self.v8_isolate()); let scope = &mut self.handle_scope(); let resolver_handle = module_map_rc .borrow_mut() .dynamic_import_map .remove(&id) .expect("Invalid dynamic import id"); let resolver = resolver_handle.open(scope); // IMPORTANT: No borrows to `ModuleMap` can be held at this point because // rejecting the promise might initiate another `import()` which will // in turn call `bindings::host_import_module_dynamically_callback` which // will reach into `ModuleMap` from within the isolate. let exception = v8::Local::new(scope, exception); resolver.reject(scope, exception).unwrap(); scope.perform_microtask_checkpoint(); } fn dynamic_import_resolve(&mut self, id: ModuleLoadId, mod_id: ModuleId) { let state_rc = self.state.clone(); let module_map_rc = Self::module_map(self.v8_isolate()); let scope = &mut self.handle_scope(); let resolver_handle = module_map_rc .borrow_mut() .dynamic_import_map .remove(&id) .expect("Invalid dynamic import id"); let resolver = resolver_handle.open(scope); let module = { module_map_rc .borrow() .get_handle(mod_id) .map(|handle| v8::Local::new(scope, handle)) .expect("Dyn import module info not found") }; // Resolution success assert_eq!(module.get_status(), v8::ModuleStatus::Evaluated); // IMPORTANT: No borrows to `ModuleMap` can be held at this point because // resolving the promise might initiate another `import()` which will // in turn call `bindings::host_import_module_dynamically_callback` which // will reach into `ModuleMap` from within the isolate. let module_namespace = module.get_module_namespace(); resolver.resolve(scope, module_namespace).unwrap(); state_rc.borrow_mut().dyn_module_evaluate_idle_counter = 0; scope.perform_microtask_checkpoint(); } fn prepare_dyn_imports( &mut self, cx: &mut Context, ) -> Poll<Result<(), Error>> { if self .get_module_map() .borrow() .preparing_dynamic_imports .is_empty() { return Poll::Ready(Ok(())); } let module_map_rc = self.get_module_map().clone(); loop { let poll_result = module_map_rc .borrow_mut() .preparing_dynamic_imports .poll_next_unpin(cx); if let Poll::Ready(Some(prepare_poll)) = poll_result { let dyn_import_id = prepare_poll.0; let prepare_result = prepare_poll.1; match prepare_result { Ok(load) => { module_map_rc .borrow_mut() .pending_dynamic_imports .push(load.into_future()); } Err(err) => { let exception = to_v8_type_error(&mut self.handle_scope(), err); self.dynamic_import_reject(dyn_import_id, exception); } } // Continue polling for more prepared dynamic imports. continue; } // There are no active dynamic import loads, or none are ready. return Poll::Ready(Ok(())); } } fn poll_dyn_imports(&mut self, cx: &mut Context) -> Poll<Result<(), Error>> { if self .get_module_map() .borrow() .pending_dynamic_imports .is_empty() { return Poll::Ready(Ok(())); } let module_map_rc = self.get_module_map().clone(); loop { let poll_result = module_map_rc .borrow_mut() .pending_dynamic_imports .poll_next_unpin(cx); if let Poll::Ready(Some(load_stream_poll)) = poll_result { let maybe_result = load_stream_poll.0; let mut load = load_stream_poll.1; let dyn_import_id = load.id; if let Some(load_stream_result) = maybe_result { match load_stream_result { Ok((request, info)) => { // A module (not necessarily the one dynamically imported) has been // fetched. Create and register it, and if successful, poll for the // next recursive-load event related to this dynamic import. let register_result = load.register_and_recurse( &mut self.handle_scope(), &request, &info, ); match register_result { Ok(()) => { // Keep importing until it's fully drained module_map_rc .borrow_mut() .pending_dynamic_imports .push(load.into_future()); } Err(err) => { let exception = match err { ModuleError::Exception(e) => e, ModuleError::Other(e) => { to_v8_type_error(&mut self.handle_scope(), e) } }; self.dynamic_import_reject(dyn_import_id, exception) } } } Err(err) => { // A non-javascript error occurred; this could be due to a an invalid // module specifier, or a problem with the source map, or a failure // to fetch the module source code. let exception = to_v8_type_error(&mut self.handle_scope(), err); self.dynamic_import_reject(dyn_import_id, exception); } } } else { // The top-level module from a dynamic import has been instantiated. // Load is done. let module_id = load.root_module_id.expect("Root module should be loaded"); let result = self.instantiate_module(module_id); if let Err(exception) = result { self.dynamic_import_reject(dyn_import_id, exception); } self.dynamic_import_module_evaluate(dyn_import_id, module_id)?; } // Continue polling for more ready dynamic imports. continue; } // There are no active dynamic import loads, or none are ready. return Poll::Ready(Ok(())); } } /// "deno_core" runs V8 with Top Level Await enabled. It means that each /// module evaluation returns a promise from V8. /// Feature docs: https://v8.dev/features/top-level-await /// /// This promise resolves after all dependent modules have also /// resolved. Each dependent module may perform calls to "import()" and APIs /// using async ops will add futures to the runtime's event loop. /// It means that the promise returned from module evaluation will /// resolve only after all futures in the event loop are done. /// /// Thus during turn of event loop we need to check if V8 has /// resolved or rejected the promise. If the promise is still pending /// then another turn of event loop must be performed. fn evaluate_pending_module(&mut self) { let maybe_module_evaluation = self.state.borrow_mut().pending_mod_evaluate.take(); if maybe_module_evaluation.is_none() { return; } let mut module_evaluation = maybe_module_evaluation.unwrap(); let state_rc = self.state.clone(); let scope = &mut self.handle_scope(); let promise_global = module_evaluation.promise.clone().unwrap(); let promise = promise_global.open(scope); let promise_state = promise.state(); match promise_state { v8::PromiseState::Pending => { // NOTE: `poll_event_loop` will decide if // runtime would be woken soon state_rc.borrow_mut().pending_mod_evaluate = Some(module_evaluation); } v8::PromiseState::Fulfilled => { scope.perform_microtask_checkpoint(); // Receiver end might have been already dropped, ignore the result let _ = module_evaluation.sender.send(Ok(())); module_evaluation.handled_promise_rejections.clear(); } v8::PromiseState::Rejected => { let exception = promise.result(scope); scope.perform_microtask_checkpoint(); // Receiver end might have been already dropped, ignore the result if module_evaluation .handled_promise_rejections .contains(&promise_global) { let _ = module_evaluation.sender.send(Ok(())); module_evaluation.handled_promise_rejections.clear(); } else { let _ = module_evaluation .sender .send(exception_to_err_result(scope, exception, false)); } } } } // Returns true if some dynamic import was resolved. fn evaluate_dyn_imports(&mut self) -> bool { let pending = std::mem::take(&mut self.state.borrow_mut().pending_dyn_mod_evaluate); if pending.is_empty() { return false; } let mut resolved_any = false; let mut still_pending = vec![]; for pending_dyn_evaluate in pending { let maybe_result = { let scope = &mut self.handle_scope(); let module_id = pending_dyn_evaluate.module_id; let promise = pending_dyn_evaluate.promise.open(scope); let _module = pending_dyn_evaluate.module.open(scope); let promise_state = promise.state(); match promise_state { v8::PromiseState::Pending => { still_pending.push(pending_dyn_evaluate); None } v8::PromiseState::Fulfilled => { Some(Ok((pending_dyn_evaluate.load_id, module_id))) } v8::PromiseState::Rejected => { let exception = promise.result(scope); let exception = v8::Global::new(scope, exception); Some(Err((pending_dyn_evaluate.load_id, exception))) } } }; if let Some(result) = maybe_result { resolved_any = true; match result { Ok((dyn_import_id, module_id)) => { self.dynamic_import_resolve(dyn_import_id, module_id); } Err((dyn_import_id, exception)) => { self.dynamic_import_reject(dyn_import_id, exception); } } } } self.state.borrow_mut().pending_dyn_mod_evaluate = still_pending; resolved_any } /// Asynchronously load specified module and all of its dependencies. /// /// The module will be marked as "main", and because of that /// "import.meta.main" will return true when checked inside that module. /// /// User must call [`JsRuntime::mod_evaluate`] with returned `ModuleId` /// manually after load is finished. pub async fn load_main_module( &mut self, specifier: &ModuleSpecifier, code: Option<String>, ) -> Result<ModuleId, Error> { let module_map_rc = Self::module_map(self.v8_isolate()); if let Some(code) = code { let scope = &mut self.handle_scope(); module_map_rc .borrow_mut() .new_es_module( scope, // main module true, specifier.as_str(), code.as_bytes(), ) .map_err(|e| match e { ModuleError::Exception(exception) => { let exception = v8::Local::new(scope, exception); exception_to_err_result::<()>(scope, exception, false).unwrap_err() } ModuleError::Other(error) => error, })?; } let mut load = ModuleMap::load_main(module_map_rc.clone(), specifier.as_str()).await?; while let Some(load_result) = load.next().await { let (request, info) = load_result?; let scope = &mut self.handle_scope(); load.register_and_recurse(scope, &request, &info).map_err( |e| match e { ModuleError::Exception(exception) => { let exception = v8::Local::new(scope, exception); exception_to_err_result::<()>(scope, exception, false).unwrap_err() } ModuleError::Other(error) => error, }, )?; } let root_id = load.root_module_id.expect("Root module should be loaded"); self.instantiate_module(root_id).map_err(|e| { let scope = &mut self.handle_scope(); let exception = v8::Local::new(scope, e); exception_to_err_result::<()>(scope, exception, false).unwrap_err() })?; Ok(root_id) } /// Asynchronously load specified ES module and all of its dependencies. /// /// This method is meant to be used when loading some utility code that /// might be later imported by the main module (ie. an entry point module). /// /// User must call [`JsRuntime::mod_evaluate`] with returned `ModuleId` /// manually after load is finished. pub async fn load_side_module( &mut self, specifier: &ModuleSpecifier, code: Option<String>, ) -> Result<ModuleId, Error> { let module_map_rc = Self::module_map(self.v8_isolate()); if let Some(code) = code { let scope = &mut self.handle_scope(); module_map_rc .borrow_mut() .new_es_module( scope, // not main module false, specifier.as_str(), code.as_bytes(), ) .map_err(|e| match e { ModuleError::Exception(exception) => { let exception = v8::Local::new(scope, exception); exception_to_err_result::<()>(scope, exception, false).unwrap_err() } ModuleError::Other(error) => error, })?; } let mut load = ModuleMap::load_side(module_map_rc.clone(), specifier.as_str()).await?; while let Some(load_result) = load.next().await { let (request, info) = load_result?; let scope = &mut self.handle_scope(); load.register_and_recurse(scope, &request, &info).map_err( |e| match e { ModuleError::Exception(exception) => { let exception = v8::Local::new(scope, exception); exception_to_err_result::<()>(scope, exception, false).unwrap_err() } ModuleError::Other(error) => error, }, )?; } let root_id = load.root_module_id.expect("Root module should be loaded"); self.instantiate_module(root_id).map_err(|e| { let scope = &mut self.handle_scope(); let exception = v8::Local::new(scope, e); exception_to_err_result::<()>(scope, exception, false).unwrap_err() })?; Ok(root_id) } fn check_promise_exceptions(&mut self) -> Result<(), Error> { let mut state = self.state.borrow_mut(); if state.pending_promise_exceptions.is_empty() { return Ok(()); } let key = { state .pending_promise_exceptions .keys() .next() .unwrap() .clone() }; let handle = state.pending_promise_exceptions.remove(&key).unwrap(); drop(state); let scope = &mut self.handle_scope(); let exception = v8::Local::new(scope, handle); exception_to_err_result(scope, exception, true) } // Send finished responses to JS fn resolve_async_ops(&mut self, cx: &mut Context) -> Result<(), Error> { let isolate = self.v8_isolate.as_mut().unwrap(); let scope = &mut self .state .borrow() .global_realm .as_ref() .unwrap() .handle_scope(isolate); // We return async responses to JS in unbounded batches (may change), // each batch is a flat vector of tuples: // `[promise_id1, op_result1, promise_id2, op_result2, ...]` // promise_id is a simple integer, op_result is an ops::OpResult // which contains a value OR an error, encoded as a tuple. // This batch is received in JS via the special `arguments` variable // and then each tuple is used to resolve or reject promises // // This can handle 16 promises (32 / 2) futures in a single batch without heap // allocations. let mut args: SmallVec<[v8::Local<v8::Value>; 32]> = SmallVec::new(); // Now handle actual ops. { let mut state = self.state.borrow_mut(); state.have_unpolled_ops = false; while let Poll::Ready(Some(item)) = state.pending_ops.poll_next_unpin(cx) { let (promise_id, op_id, mut resp) = item; state.unrefed_ops.remove(&promise_id); state.op_state.borrow().tracker.track_async_completed(op_id); args.push(v8::Integer::new(scope, promise_id as i32).into()); args.push(match resp.to_v8(scope) { Ok(v) => v, Err(e) => OpResult::Err(OpError::new(&|_| "TypeError", e.into())) .to_v8(scope) .unwrap(), }); } } if args.is_empty() { return Ok(()); } let js_recv_cb_handle = self.state.borrow().js_recv_cb.clone().unwrap(); let tc_scope = &mut v8::TryCatch::new(scope); let js_recv_cb = js_recv_cb_handle.open(tc_scope); let this = v8::undefined(tc_scope).into(); js_recv_cb.call(tc_scope, this, args.as_slice()); match tc_scope.exception() { None => Ok(()), Some(exception) => exception_to_err_result(tc_scope, exception, false), } } fn drain_macrotasks(&mut self) -> Result<(), Error> { if self.state.borrow().js_macrotask_cbs.is_empty() { return Ok(()); } let js_macrotask_cb_handles = self.state.borrow().js_macrotask_cbs.clone(); let scope = &mut self.handle_scope(); for js_macrotask_cb_handle in js_macrotask_cb_handles { let js_macrotask_cb = js_macrotask_cb_handle.open(scope); // Repeatedly invoke macrotask callback until it returns true (done), // such that ready microtasks would be automatically run before // next macrotask is processed. let tc_scope = &mut v8::TryCatch::new(scope); let this = v8::undefined(tc_scope).into(); loop { let is_done = js_macrotask_cb.call(tc_scope, this, &[]); if let Some(exception) = tc_scope.exception() { return exception_to_err_result(tc_scope, exception, false); } if tc_scope.has_terminated() || tc_scope.is_execution_terminating() { return Ok(()); } let is_done = is_done.unwrap(); if is_done.is_true() { break; } } } Ok(()) } fn drain_nexttick(&mut self) -> Result<(), Error> { if self.state.borrow().js_nexttick_cbs.is_empty() { return Ok(()); } let state = self.state.clone(); if !state.borrow().has_tick_scheduled { let scope = &mut self.handle_scope(); scope.perform_microtask_checkpoint(); } // TODO(bartlomieju): Node also checks for absence of "rejection_to_warn" if !state.borrow().has_tick_scheduled { return Ok(()); } let js_nexttick_cb_handles = state.borrow().js_nexttick_cbs.clone(); let scope = &mut self.handle_scope(); for js_nexttick_cb_handle in js_nexttick_cb_handles { let js_nexttick_cb = js_nexttick_cb_handle.open(scope); let tc_scope = &mut v8::TryCatch::new(scope); let this = v8::undefined(tc_scope).into(); js_nexttick_cb.call(tc_scope, this, &[]); if let Some(exception) = tc_scope.exception() { return exception_to_err_result(tc_scope, exception, false); } } Ok(()) }}
/// 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 if with a reference to a/// [`v8::Isolate`] other than the one that corresponds to the current context.////// # 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)]pub struct JsRealm(v8::Global<v8::Context>);impl JsRealm { pub fn new(context: v8::Global<v8::Context>) -> Self { JsRealm(context) } pub fn context(&self) -> &v8::Global<v8::Context> { &self.0 } pub fn handle_scope<'s>( &self, isolate: &'s mut v8::Isolate, ) -> v8::HandleScope<'s> { v8::HandleScope::with_context(isolate, &self.0) } pub fn global_object<'s>( &self, isolate: &'s mut v8::Isolate, ) -> v8::Local<'s, v8::Object> { let scope = &mut self.handle_scope(isolate); self.0.open(scope).global(scope) } /// Executes traditional JavaScript code (traditional = not ES modules) in the /// realm's context. /// /// `name` can be a filepath or any other string, eg. /// /// - "/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: &str, source_code: &str, ) -> Result<v8::Global<v8::Value>, Error> { let scope = &mut self.handle_scope(isolate); let source = v8::String::new(scope, source_code).unwrap(); let name = v8::String::new(scope, name).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`}
#[inline]pub fn queue_fast_async_op( ctx: &OpCtx, op: impl Future<Output = (PromiseId, OpId, OpResult)> + 'static,) { let runtime_state = match ctx.runtime_state.upgrade() { Some(rc_state) => rc_state, // atleast 1 Rc is held by the JsRuntime. None => unreachable!(), }; let mut state = runtime_state.borrow_mut(); state.pending_ops.push(OpCall::lazy(op)); state.have_unpolled_ops = true;}
#[inline]pub fn queue_async_op( ctx: &OpCtx, scope: &mut v8::HandleScope, deferred: bool, op: impl Future<Output = (PromiseId, OpId, OpResult)> + 'static,) { let runtime_state = match ctx.runtime_state.upgrade() { Some(rc_state) => rc_state, // atleast 1 Rc is held by the JsRuntime. None => unreachable!(), }; match OpCall::eager(op) { // This calls promise.resolve() before the control goes back to userland JS. It works something // along the lines of: // // function opresolve(promiseId, ...) { // getPromise(promiseId).resolve(...); // } // const p = setPromise(); // op.op_async(promiseId, ...); // Calls `opresolve` // return p; EagerPollResult::Ready((promise_id, op_id, mut resp)) if !deferred => { let args = &[ v8::Integer::new(scope, promise_id).into(), resp.to_v8(scope).unwrap(), ]; let js_recv_cb_handle = runtime_state.borrow().js_recv_cb.clone().unwrap(); ctx.state.borrow_mut().tracker.track_async_completed(op_id); let tc_scope = &mut v8::TryCatch::new(scope); let js_recv_cb = js_recv_cb_handle.open(tc_scope); let this = v8::undefined(tc_scope).into(); js_recv_cb.call(tc_scope, this, args); } EagerPollResult::Ready(op) => { let ready = OpCall::ready(op); let mut state = runtime_state.borrow_mut(); state.pending_ops.push(ready); state.have_unpolled_ops = true; } EagerPollResult::Pending(op) => { let mut state = runtime_state.borrow_mut(); state.pending_ops.push(op); state.have_unpolled_ops = true; } }}
#[cfg(test)]pub mod tests { use super::*; use crate::error::custom_error; use crate::error::AnyError; use crate::modules::ModuleSource; use crate::modules::ModuleSourceFuture; use crate::modules::ModuleType; use crate::ZeroCopyBuf; use deno_ops::op; use futures::future::lazy; use std::ops::FnOnce; use std::pin::Pin; use std::rc::Rc; use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::Arc; // deno_ops macros generate code assuming deno_core in scope. mod deno_core { pub use crate::*; } pub fn run_in_task<F>(f: F) where F: FnOnce(&mut Context) + Send + 'static, { futures::executor::block_on(lazy(move |cx| f(cx))); } #[derive(Copy, Clone)] enum Mode { Async, AsyncDeferred, AsyncZeroCopy(bool), } struct TestState { mode: Mode, dispatch_count: Arc<AtomicUsize>, } #[op] async fn op_test( rc_op_state: Rc<RefCell<OpState>>, control: u8, buf: Option<ZeroCopyBuf>, ) -> Result<u8, AnyError> { #![allow(clippy::await_holding_refcell_ref)] // False positive. let op_state_ = rc_op_state.borrow(); let test_state = op_state_.borrow::<TestState>(); test_state.dispatch_count.fetch_add(1, Ordering::Relaxed); let mode = test_state.mode; drop(op_state_); match mode { Mode::Async => { assert_eq!(control, 42); Ok(43) } Mode::AsyncDeferred => { tokio::task::yield_now().await; assert_eq!(control, 42); Ok(43) } Mode::AsyncZeroCopy(has_buffer) => { assert_eq!(buf.is_some(), has_buffer); if let Some(buf) = buf { assert_eq!(buf.len(), 1); } Ok(43) } } } fn setup(mode: Mode) -> (JsRuntime, Arc<AtomicUsize>) { let dispatch_count = Arc::new(AtomicUsize::new(0)); let dispatch_count2 = dispatch_count.clone(); let ext = Extension::builder() .ops(vec![op_test::decl()]) .state(move |state| { state.put(TestState { mode, dispatch_count: dispatch_count2.clone(), }); Ok(()) }) .build(); let mut runtime = JsRuntime::new(RuntimeOptions { extensions: vec![ext], get_error_class_fn: Some(&|error| { crate::error::get_custom_error_class(error).unwrap() }), ..Default::default() }); runtime .execute_script( "setup.js", r#" function assert(cond) { if (!cond) { throw Error("assert"); } } "#, ) .unwrap(); assert_eq!(dispatch_count.load(Ordering::Relaxed), 0); (runtime, dispatch_count) } #[test] fn test_ref_unref_ops() { let (mut runtime, _dispatch_count) = setup(Mode::AsyncDeferred); runtime .execute_script( "filename.js", r#" Deno.core.initializeAsyncOps(); var promiseIdSymbol = Symbol.for("Deno.core.internalPromiseId"); var p1 = Deno.core.ops.op_test(42); var p2 = Deno.core.ops.op_test(42); "#, ) .unwrap(); { let isolate = runtime.v8_isolate(); let state_rc = JsRuntime::state(isolate); let state = state_rc.borrow(); assert_eq!(state.pending_ops.len(), 2); assert_eq!(state.unrefed_ops.len(), 0); } runtime .execute_script( "filename.js", r#" Deno.core.ops.op_unref_op(p1[promiseIdSymbol]); Deno.core.ops.op_unref_op(p2[promiseIdSymbol]); "#, ) .unwrap(); { let isolate = runtime.v8_isolate(); let state_rc = JsRuntime::state(isolate); let state = state_rc.borrow(); assert_eq!(state.pending_ops.len(), 2); assert_eq!(state.unrefed_ops.len(), 2); } runtime .execute_script( "filename.js", r#" Deno.core.ops.op_ref_op(p1[promiseIdSymbol]); Deno.core.ops.op_ref_op(p2[promiseIdSymbol]); "#, ) .unwrap(); { let isolate = runtime.v8_isolate(); let state_rc = JsRuntime::state(isolate); let state = state_rc.borrow(); assert_eq!(state.pending_ops.len(), 2); assert_eq!(state.unrefed_ops.len(), 0); } } #[test] fn test_dispatch() { let (mut runtime, dispatch_count) = setup(Mode::Async); runtime .execute_script( "filename.js", r#" let control = 42; Deno.core.initializeAsyncOps(); Deno.core.opAsync("op_test", control); async function main() { Deno.core.opAsync("op_test", control); } main(); "#, ) .unwrap(); assert_eq!(dispatch_count.load(Ordering::Relaxed), 2); } #[test] fn test_op_async_promise_id() { let (mut runtime, _dispatch_count) = setup(Mode::Async); runtime .execute_script( "filename.js", r#" Deno.core.initializeAsyncOps(); const p = Deno.core.opAsync("op_test", 42); if (p[Symbol.for("Deno.core.internalPromiseId")] == undefined) { throw new Error("missing id on returned promise"); } "#, ) .unwrap(); } #[test] fn test_dispatch_no_zero_copy_buf() { let (mut runtime, dispatch_count) = setup(Mode::AsyncZeroCopy(false)); runtime .execute_script( "filename.js", r#" Deno.core.initializeAsyncOps(); Deno.core.opAsync("op_test"); "#, ) .unwrap(); assert_eq!(dispatch_count.load(Ordering::Relaxed), 1); } #[test] fn test_dispatch_stack_zero_copy_bufs() { let (mut runtime, dispatch_count) = setup(Mode::AsyncZeroCopy(true)); runtime .execute_script( "filename.js", r#" Deno.core.initializeAsyncOps(); let zero_copy_a = new Uint8Array([0]); Deno.core.opAsync("op_test", null, zero_copy_a); "#, ) .unwrap(); assert_eq!(dispatch_count.load(Ordering::Relaxed), 1); } #[test] fn test_execute_script_return_value() { let mut runtime = JsRuntime::new(Default::default()); let value_global = runtime.execute_script("a.js", "a = 1 + 2").unwrap(); { let scope = &mut runtime.handle_scope(); let value = value_global.open(scope); assert_eq!(value.integer_value(scope).unwrap(), 3); } let value_global = runtime.execute_script("b.js", "b = 'foobar'").unwrap(); { let scope = &mut runtime.handle_scope(); let value = value_global.open(scope); assert!(value.is_string()); assert_eq!( value.to_string(scope).unwrap().to_rust_string_lossy(scope), "foobar" ); } } #[tokio::test] async fn test_poll_value() { run_in_task(|cx| { let mut runtime = JsRuntime::new(Default::default()); let value_global = runtime .execute_script("a.js", "Promise.resolve(1 + 2)") .unwrap(); let v = runtime.poll_value(&value_global, cx); { let scope = &mut runtime.handle_scope(); assert!( matches!(v, Poll::Ready(Ok(v)) if v.open(scope).integer_value(scope).unwrap() == 3) ); } let value_global = runtime .execute_script( "a.js", "Promise.resolve(new Promise(resolve => resolve(2 + 2)))", ) .unwrap(); let v = runtime.poll_value(&value_global, cx); { let scope = &mut runtime.handle_scope(); assert!( matches!(v, Poll::Ready(Ok(v)) if v.open(scope).integer_value(scope).unwrap() == 4) ); } let value_global = runtime .execute_script("a.js", "Promise.reject(new Error('fail'))") .unwrap(); let v = runtime.poll_value(&value_global, cx); assert!( matches!(v, Poll::Ready(Err(e)) if e.downcast_ref::<JsError>().unwrap().exception_message == "Uncaught Error: fail") ); let value_global = runtime .execute_script("a.js", "new Promise(resolve => {})") .unwrap(); let v = runtime.poll_value(&value_global, cx); matches!(v, Poll::Ready(Err(e)) if e.to_string() == "Promise resolution is still pending but the event loop has already resolved."); }); } #[tokio::test] async fn test_resolve_value() { let mut runtime = JsRuntime::new(Default::default()); let value_global = runtime .execute_script("a.js", "Promise.resolve(1 + 2)") .unwrap(); let result_global = runtime.resolve_value(value_global).await.unwrap(); { let scope = &mut runtime.handle_scope(); let value = result_global.open(scope); assert_eq!(value.integer_value(scope).unwrap(), 3); } let value_global = runtime .execute_script( "a.js", "Promise.resolve(new Promise(resolve => resolve(2 + 2)))", ) .unwrap(); let result_global = runtime.resolve_value(value_global).await.unwrap(); { let scope = &mut runtime.handle_scope(); let value = result_global.open(scope); assert_eq!(value.integer_value(scope).unwrap(), 4); } let value_global = runtime .execute_script("a.js", "Promise.reject(new Error('fail'))") .unwrap(); let err = runtime.resolve_value(value_global).await.unwrap_err(); assert_eq!( "Uncaught Error: fail", err.downcast::<JsError>().unwrap().exception_message ); let value_global = runtime .execute_script("a.js", "new Promise(resolve => {})") .unwrap(); let error_string = runtime .resolve_value(value_global) .await .unwrap_err() .to_string(); assert_eq!( "Promise resolution is still pending but the event loop has already resolved.", error_string, ); } #[test] fn terminate_execution_webassembly() { let (mut runtime, _dispatch_count) = setup(Mode::Async); let v8_isolate_handle = runtime.v8_isolate().thread_safe_handle(); // Run an infinite loop in Webassemby code, which should be terminated. let promise = runtime.execute_script("infinite_wasm_loop.js", r#" (async () => { const wasmCode = new Uint8Array([ 0, 97, 115, 109, 1, 0, 0, 0, 1, 4, 1, 96, 0, 0, 3, 2, 1, 0, 7, 17, 1, 13, 105, 110, 102, 105, 110, 105, 116, 101, 95, 108, 111, 111, 112, 0, 0, 10, 9, 1, 7, 0, 3, 64, 12, 0, 11, 11, ]); const wasmModule = await WebAssembly.compile(wasmCode); globalThis.wasmInstance = new WebAssembly.Instance(wasmModule); })() "#).unwrap(); futures::executor::block_on(runtime.resolve_value(promise)).unwrap(); let terminator_thread = std::thread::spawn(move || { std::thread::sleep(std::time::Duration::from_millis(1000)); // terminate execution let ok = v8_isolate_handle.terminate_execution(); assert!(ok); }); let err = runtime .execute_script( "infinite_wasm_loop2.js", "globalThis.wasmInstance.exports.infinite_loop();", ) .unwrap_err(); assert_eq!(err.to_string(), "Uncaught Error: execution terminated"); // Cancel the execution-terminating exception in order to allow script // execution again. let ok = runtime.v8_isolate().cancel_terminate_execution(); assert!(ok); // Verify that the isolate usable again. runtime .execute_script("simple.js", "1 + 1") .expect("execution should be possible again"); terminator_thread.join().unwrap(); } #[test] fn terminate_execution() { let (mut isolate, _dispatch_count) = setup(Mode::Async); let v8_isolate_handle = isolate.v8_isolate().thread_safe_handle(); let terminator_thread = std::thread::spawn(move || { // allow deno to boot and run std::thread::sleep(std::time::Duration::from_millis(100)); // terminate execution let ok = v8_isolate_handle.terminate_execution(); assert!(ok); }); // Rn an infinite loop, which should be terminated. match isolate.execute_script("infinite_loop.js", "for(;;) {}") { Ok(_) => panic!("execution should be terminated"), Err(e) => { assert_eq!(e.to_string(), "Uncaught Error: execution terminated") } }; // Cancel the execution-terminating exception in order to allow script // execution again. let ok = isolate.v8_isolate().cancel_terminate_execution(); assert!(ok); // Verify that the isolate usable again. isolate .execute_script("simple.js", "1 + 1") .expect("execution should be possible again"); terminator_thread.join().unwrap(); } #[test] fn dangling_shared_isolate() { let v8_isolate_handle = { // isolate is dropped at the end of this block let (mut runtime, _dispatch_count) = setup(Mode::Async); runtime.v8_isolate().thread_safe_handle() }; // this should not SEGFAULT v8_isolate_handle.terminate_execution(); } #[test] fn syntax_error() { let mut runtime = JsRuntime::new(Default::default()); let src = "hocuspocus("; let r = runtime.execute_script("i.js", src); let e = r.unwrap_err(); let js_error = e.downcast::<JsError>().unwrap(); let frame = js_error.frames.first().unwrap(); assert_eq!(frame.column_number, Some(12)); } #[test] fn test_encode_decode() { run_in_task(|cx| { let (mut runtime, _dispatch_count) = setup(Mode::Async); runtime .execute_script( "encode_decode_test.js", include_str!("encode_decode_test.js"), ) .unwrap(); if let Poll::Ready(Err(_)) = runtime.poll_event_loop(cx, false) { unreachable!(); } }); } #[test] fn test_serialize_deserialize() { run_in_task(|cx| { let (mut runtime, _dispatch_count) = setup(Mode::Async); runtime .execute_script( "serialize_deserialize_test.js", include_str!("serialize_deserialize_test.js"), ) .unwrap(); if let Poll::Ready(Err(_)) = runtime.poll_event_loop(cx, false) { unreachable!(); } }); } #[test] fn test_error_builder() { #[op] fn op_err() -> Result<(), Error> { Err(custom_error("DOMExceptionOperationError", "abc")) } pub fn get_error_class_name(_: &Error) -> &'static str { "DOMExceptionOperationError" } run_in_task(|cx| { let ext = Extension::builder().ops(vec![op_err::decl()]).build(); let mut runtime = JsRuntime::new(RuntimeOptions { extensions: vec![ext], get_error_class_fn: Some(&get_error_class_name), ..Default::default() }); runtime .execute_script( "error_builder_test.js", include_str!("error_builder_test.js"), ) .unwrap(); if let Poll::Ready(Err(_)) = runtime.poll_event_loop(cx, false) { unreachable!(); } }); } #[test] fn will_snapshot() { let snapshot = { let mut runtime = JsRuntime::new(RuntimeOptions { will_snapshot: true, ..Default::default() }); runtime.execute_script("a.js", "a = 1 + 2").unwrap(); runtime.snapshot() }; let snapshot = Snapshot::JustCreated(snapshot); let mut runtime2 = JsRuntime::new(RuntimeOptions { startup_snapshot: Some(snapshot), ..Default::default() }); runtime2 .execute_script("check.js", "if (a != 3) throw Error('x')") .unwrap(); } #[test] fn will_snapshot2() { let startup_data = { let mut runtime = JsRuntime::new(RuntimeOptions { will_snapshot: true, ..Default::default() }); runtime.execute_script("a.js", "let a = 1 + 2").unwrap(); runtime.snapshot() }; let snapshot = Snapshot::JustCreated(startup_data); let mut runtime = JsRuntime::new(RuntimeOptions { will_snapshot: true, startup_snapshot: Some(snapshot), ..Default::default() }); let startup_data = { runtime .execute_script("check_a.js", "if (a != 3) throw Error('x')") .unwrap(); runtime.execute_script("b.js", "b = 2 + 3").unwrap(); runtime.snapshot() }; let snapshot = Snapshot::JustCreated(startup_data); { let mut runtime = JsRuntime::new(RuntimeOptions { startup_snapshot: Some(snapshot), ..Default::default() }); runtime .execute_script("check_b.js", "if (b != 5) throw Error('x')") .unwrap(); runtime .execute_script("check2.js", "if (!Deno.core) throw Error('x')") .unwrap(); } } #[test] fn test_snapshot_callbacks() { let snapshot = { let mut runtime = JsRuntime::new(RuntimeOptions { will_snapshot: true, ..Default::default() }); runtime .execute_script( "a.js", r#" Deno.core.ops.op_set_macrotask_callback(() => { return true; }); Deno.core.ops.op_set_format_exception_callback(()=> { return null; }) Deno.core.setPromiseRejectCallback(() => { return false; }); a = 1 + 2; "#, ) .unwrap(); runtime.snapshot() }; let snapshot = Snapshot::JustCreated(snapshot); let mut runtime2 = JsRuntime::new(RuntimeOptions { startup_snapshot: Some(snapshot), ..Default::default() }); runtime2 .execute_script("check.js", "if (a != 3) throw Error('x')") .unwrap(); } #[test] fn test_from_boxed_snapshot() { let snapshot = { let mut runtime = JsRuntime::new(RuntimeOptions { will_snapshot: true, ..Default::default() }); runtime.execute_script("a.js", "a = 1 + 2").unwrap(); let snap: &[u8] = &runtime.snapshot(); Vec::from(snap).into_boxed_slice() }; let snapshot = Snapshot::Boxed(snapshot); let mut runtime2 = JsRuntime::new(RuntimeOptions { startup_snapshot: Some(snapshot), ..Default::default() }); runtime2 .execute_script("check.js", "if (a != 3) throw Error('x')") .unwrap(); } #[test] fn test_get_module_namespace() { #[derive(Default)] struct ModsLoader; impl ModuleLoader for ModsLoader { fn resolve( &self, specifier: &str, referrer: &str, _is_main: bool, ) -> Result<ModuleSpecifier, Error> { assert_eq!(specifier, "file:///main.js"); assert_eq!(referrer, "."); let s = crate::resolve_import(specifier, referrer).unwrap(); Ok(s) } fn load( &self, _module_specifier: &ModuleSpecifier, _maybe_referrer: Option<ModuleSpecifier>, _is_dyn_import: bool, ) -> Pin<Box<ModuleSourceFuture>> { async { Err(generic_error("Module loading is not supported")) } .boxed_local() } } let loader = std::rc::Rc::new(ModsLoader::default()); let mut runtime = JsRuntime::new(RuntimeOptions { module_loader: Some(loader), ..Default::default() }); let specifier = crate::resolve_url("file:///main.js").unwrap(); let source_code = r#" export const a = "b"; export default 1 + 2; "# .to_string(); let module_id = futures::executor::block_on( runtime.load_main_module(&specifier, Some(source_code)), ) .unwrap(); let _ = runtime.mod_evaluate(module_id); let module_namespace = runtime.get_module_namespace(module_id).unwrap(); let scope = &mut runtime.handle_scope(); let module_namespace = v8::Local::<v8::Object>::new(scope, module_namespace); assert!(module_namespace.is_module_namespace_object()); let unknown_export_name = v8::String::new(scope, "none").unwrap(); let binding = module_namespace.get(scope, unknown_export_name.into()); assert!(binding.is_some()); assert!(binding.unwrap().is_undefined()); let empty_export_name = v8::String::new(scope, "").unwrap(); let binding = module_namespace.get(scope, empty_export_name.into()); assert!(binding.is_some()); assert!(binding.unwrap().is_undefined()); let a_export_name = v8::String::new(scope, "a").unwrap(); let binding = module_namespace.get(scope, a_export_name.into()); assert!(binding.unwrap().is_string()); assert_eq!(binding.unwrap(), v8::String::new(scope, "b").unwrap()); let default_export_name = v8::String::new(scope, "default").unwrap(); let binding = module_namespace.get(scope, default_export_name.into()); assert!(binding.unwrap().is_number()); assert_eq!(binding.unwrap(), v8::Number::new(scope, 3_f64)); } #[test] fn test_heap_limits() { let create_params = v8::Isolate::create_params().heap_limits(0, 3 * 1024 * 1024); let mut runtime = JsRuntime::new(RuntimeOptions { create_params: Some(create_params), ..Default::default() }); let cb_handle = runtime.v8_isolate().thread_safe_handle(); let callback_invoke_count = Rc::new(AtomicUsize::new(0)); let inner_invoke_count = Rc::clone(&callback_invoke_count); runtime.add_near_heap_limit_callback( move |current_limit, _initial_limit| { inner_invoke_count.fetch_add(1, Ordering::SeqCst); cb_handle.terminate_execution(); current_limit * 2 }, ); let err = runtime .execute_script( "script name", r#"let s = ""; while(true) { s += "Hello"; }"#, ) .expect_err("script should fail"); assert_eq!( "Uncaught Error: execution terminated", err.downcast::<JsError>().unwrap().exception_message ); assert!(callback_invoke_count.load(Ordering::SeqCst) > 0) } #[test] fn test_heap_limit_cb_remove() { let mut runtime = JsRuntime::new(Default::default()); runtime.add_near_heap_limit_callback(|current_limit, _initial_limit| { current_limit * 2 }); runtime.remove_near_heap_limit_callback(3 * 1024 * 1024); assert!(runtime.allocations.near_heap_limit_callback_data.is_none()); } #[test] fn test_heap_limit_cb_multiple() { let create_params = v8::Isolate::create_params().heap_limits(0, 3 * 1024 * 1024); let mut runtime = JsRuntime::new(RuntimeOptions { create_params: Some(create_params), ..Default::default() }); let cb_handle = runtime.v8_isolate().thread_safe_handle(); let callback_invoke_count_first = Rc::new(AtomicUsize::new(0)); let inner_invoke_count_first = Rc::clone(&callback_invoke_count_first); runtime.add_near_heap_limit_callback( move |current_limit, _initial_limit| { inner_invoke_count_first.fetch_add(1, Ordering::SeqCst); current_limit * 2 }, ); let callback_invoke_count_second = Rc::new(AtomicUsize::new(0)); let inner_invoke_count_second = Rc::clone(&callback_invoke_count_second); runtime.add_near_heap_limit_callback( move |current_limit, _initial_limit| { inner_invoke_count_second.fetch_add(1, Ordering::SeqCst); cb_handle.terminate_execution(); current_limit * 2 }, ); let err = runtime .execute_script( "script name", r#"let s = ""; while(true) { s += "Hello"; }"#, ) .expect_err("script should fail"); assert_eq!( "Uncaught Error: execution terminated", err.downcast::<JsError>().unwrap().exception_message ); assert_eq!(0, callback_invoke_count_first.load(Ordering::SeqCst)); assert!(callback_invoke_count_second.load(Ordering::SeqCst) > 0); } #[test] fn es_snapshot() { #[derive(Default)] struct ModsLoader; impl ModuleLoader for ModsLoader { fn resolve( &self, specifier: &str, referrer: &str, _is_main: bool, ) -> Result<ModuleSpecifier, Error> { assert_eq!(specifier, "file:///main.js"); assert_eq!(referrer, "."); let s = crate::resolve_import(specifier, referrer).unwrap(); Ok(s) } fn load( &self, _module_specifier: &ModuleSpecifier, _maybe_referrer: Option<ModuleSpecifier>, _is_dyn_import: bool, ) -> Pin<Box<ModuleSourceFuture>> { unreachable!() } } let loader = std::rc::Rc::new(ModsLoader::default()); let mut runtime = JsRuntime::new(RuntimeOptions { module_loader: Some(loader), will_snapshot: true, ..Default::default() }); let specifier = crate::resolve_url("file:///main.js").unwrap(); let source_code = "Deno.core.print('hello\\n')".to_string(); let module_id = futures::executor::block_on( runtime.load_main_module(&specifier, Some(source_code)), ) .unwrap(); let _ = runtime.mod_evaluate(module_id); futures::executor::block_on(runtime.run_event_loop(false)).unwrap(); let _snapshot = runtime.snapshot(); } #[test] fn test_error_without_stack() { let mut runtime = JsRuntime::new(RuntimeOptions::default()); // SyntaxError let result = runtime.execute_script( "error_without_stack.js", r#"function main() { console.log("asdf);}main();"#, ); let expected_error = r#"Uncaught SyntaxError: Invalid or unexpected token at error_without_stack.js:3:15"#; assert_eq!(result.unwrap_err().to_string(), expected_error); } #[test] fn test_error_stack() { let mut runtime = JsRuntime::new(RuntimeOptions::default()); let result = runtime.execute_script( "error_stack.js", r#"function assert(cond) { if (!cond) { throw Error("assert"); }}function main() { assert(false);}main(); "#, ); let expected_error = r#"Error: assert at assert (error_stack.js:4:11) at main (error_stack.js:8:3) at error_stack.js:10:1"#; assert_eq!(result.unwrap_err().to_string(), expected_error); } #[test] fn test_error_async_stack() { run_in_task(|cx| { let mut runtime = JsRuntime::new(RuntimeOptions::default()); runtime .execute_script( "error_async_stack.js", r#"(async () => { const p = (async () => { await Promise.resolve().then(() => { throw new Error("async"); }); })(); try { await p; } catch (error) { console.log(error.stack); throw error; }})();"#, ) .unwrap(); let expected_error = r#"Error: async at error_async_stack.js:5:13 at async error_async_stack.js:4:5 at async error_async_stack.js:9:5"#; match runtime.poll_event_loop(cx, false) { Poll::Ready(Err(e)) => { assert_eq!(e.to_string(), expected_error); } _ => panic!(), }; }) } #[test] fn test_error_context() { use anyhow::anyhow; #[op] fn op_err_sync() -> Result<(), Error> { Err(anyhow!("original sync error").context("higher-level sync error")) } #[op] async fn op_err_async() -> Result<(), Error> { Err(anyhow!("original async error").context("higher-level async error")) } run_in_task(|cx| { let ext = Extension::builder() .ops(vec![op_err_sync::decl(), op_err_async::decl()]) .build(); let mut runtime = JsRuntime::new(RuntimeOptions { extensions: vec![ext], ..Default::default() }); runtime .execute_script( "test_error_context_sync.js", r#"let errMessage;try { Deno.core.ops.op_err_sync();} catch (err) { errMessage = err.message;}if (errMessage !== "higher-level sync error: original sync error") { throw new Error("unexpected error message from op_err_sync: " + errMessage);}"#, ) .unwrap(); let promise = runtime .execute_script( "test_error_context_async.js", r#"Deno.core.initializeAsyncOps();(async () => { let errMessage; try { await Deno.core.opAsync("op_err_async"); } catch (err) { errMessage = err.message; } if (errMessage !== "higher-level async error: original async error") { throw new Error("unexpected error message from op_err_async: " + errMessage); }})()"#, ) .unwrap(); match runtime.poll_value(&promise, cx) { Poll::Ready(Ok(_)) => {} Poll::Ready(Err(err)) => panic!("{:?}", err), _ => panic!(), } }) } #[test] fn test_pump_message_loop() { run_in_task(|cx| { let mut runtime = JsRuntime::new(RuntimeOptions::default()); runtime .execute_script( "pump_message_loop.js", r#"function assertEquals(a, b) { if (a === b) return; throw a + " does not equal " + b;}const sab = new SharedArrayBuffer(16);const i32a = new Int32Array(sab);globalThis.resolved = false;(function() { const result = Atomics.waitAsync(i32a, 0, 0); result.value.then( (value) => { assertEquals("ok", value); globalThis.resolved = true; }, () => { assertUnreachable(); });})();const notify_return_value = Atomics.notify(i32a, 0, 1);assertEquals(1, notify_return_value);"#, ) .unwrap(); match runtime.poll_event_loop(cx, false) { Poll::Ready(Ok(())) => {} _ => panic!(), }; // noop script, will resolve promise from first script runtime .execute_script("pump_message_loop2.js", r#"assertEquals(1, 1);"#) .unwrap(); // check that promise from `Atomics.waitAsync` has been resolved runtime .execute_script( "pump_message_loop3.js", r#"assertEquals(globalThis.resolved, true);"#, ) .unwrap(); }) } #[test] fn test_core_js_stack_frame() { let mut runtime = JsRuntime::new(RuntimeOptions::default()); // Call non-existent op so we get error from `core.js` let error = runtime .execute_script( "core_js_stack_frame.js", "Deno.core.opAsync('non_existent');", ) .unwrap_err(); let error_string = error.to_string(); // Test that the script specifier is a URL: `deno:<repo-relative path>`. assert!(error_string.contains("deno:core/01_core.js")); } #[test] fn test_v8_platform() { let options = RuntimeOptions { v8_platform: Some(v8::new_default_platform(0, false).make_shared()), ..Default::default() }; let mut runtime = JsRuntime::new(options); runtime.execute_script("<none>", "").unwrap(); } #[ignore] // TODO(@littledivy): Fast API ops when snapshot is not loaded. #[test] fn test_is_proxy() { let mut runtime = JsRuntime::new(RuntimeOptions::default()); let all_true: v8::Global<v8::Value> = runtime .execute_script( "is_proxy.js", r#" (function () { const o = { a: 1, b: 2}; const p = new Proxy(o, {}); return Deno.core.ops.op_is_proxy(p) && !Deno.core.ops.op_is_proxy(o) && !Deno.core.ops.op_is_proxy(42); })() "#, ) .unwrap(); let mut scope = runtime.handle_scope(); let all_true = v8::Local::<v8::Value>::new(&mut scope, &all_true); assert!(all_true.is_true()); } #[tokio::test] async fn test_async_opstate_borrow() { struct InnerState(u64); #[op] async fn op_async_borrow( op_state: Rc<RefCell<OpState>>, ) -> Result<(), Error> { let n = { let op_state = op_state.borrow(); let inner_state = op_state.borrow::<InnerState>(); inner_state.0 }; // Future must be Poll::Pending on first call tokio::time::sleep(std::time::Duration::from_millis(1)).await; if n != 42 { unreachable!(); } Ok(()) } let extension = Extension::builder() .ops(vec![op_async_borrow::decl()]) .state(|state| { state.put(InnerState(42)); Ok(()) }) .build(); let mut runtime = JsRuntime::new(RuntimeOptions { extensions: vec![extension], ..Default::default() }); runtime .execute_script( "op_async_borrow.js", "Deno.core.initializeAsyncOps(); Deno.core.ops.op_async_borrow()", ) .unwrap(); runtime.run_event_loop(false).await.unwrap(); } #[tokio::test] async fn test_sync_op_serialize_object_with_numbers_as_keys() { #[op] fn op_sync_serialize_object_with_numbers_as_keys( value: serde_json::Value, ) -> Result<(), Error> { assert_eq!( value.to_string(), r#"{"lines":{"100":{"unit":"m"},"200":{"unit":"cm"}}}"# ); Ok(()) } let extension = Extension::builder() .ops(vec![op_sync_serialize_object_with_numbers_as_keys::decl()]) .build(); let mut runtime = JsRuntime::new(RuntimeOptions { extensions: vec![extension], ..Default::default() }); runtime .execute_script( "op_sync_serialize_object_with_numbers_as_keys.js", r#"Deno.core.ops.op_sync_serialize_object_with_numbers_as_keys({ lines: { 100: { unit: "m" }, 200: { unit: "cm" } }})"#, ) .unwrap(); runtime.run_event_loop(false).await.unwrap(); } #[tokio::test] async fn test_async_op_serialize_object_with_numbers_as_keys() { #[op] async fn op_async_serialize_object_with_numbers_as_keys( value: serde_json::Value, ) -> Result<(), Error> { assert_eq!( value.to_string(), r#"{"lines":{"100":{"unit":"m"},"200":{"unit":"cm"}}}"# ); Ok(()) } let extension = Extension::builder() .ops(vec![op_async_serialize_object_with_numbers_as_keys::decl()]) .build(); let mut runtime = JsRuntime::new(RuntimeOptions { extensions: vec![extension], ..Default::default() }); runtime .execute_script( "op_async_serialize_object_with_numbers_as_keys.js", r#"Deno.core.initializeAsyncOps();Deno.core.ops.op_async_serialize_object_with_numbers_as_keys({ lines: { 100: { unit: "m" }, 200: { unit: "cm" } }})"#, ) .unwrap(); runtime.run_event_loop(false).await.unwrap(); } #[tokio::test] async fn test_set_macrotask_callback_set_next_tick_callback() { #[op] async fn op_async_sleep() -> Result<(), Error> { // Future must be Poll::Pending on first call tokio::time::sleep(std::time::Duration::from_millis(1)).await; Ok(()) } let extension = Extension::builder() .ops(vec![op_async_sleep::decl()]) .build(); let mut runtime = JsRuntime::new(RuntimeOptions { extensions: vec![extension], ..Default::default() }); runtime .execute_script( "macrotasks_and_nextticks.js", r#" Deno.core.initializeAsyncOps(); (async function () { const results = []; Deno.core.ops.op_set_macrotask_callback(() => { results.push("macrotask"); return true; }); Deno.core.ops.op_set_next_tick_callback(() => { results.push("nextTick"); Deno.core.ops.op_set_has_tick_scheduled(false); }); Deno.core.ops.op_set_has_tick_scheduled(true); await Deno.core.opAsync('op_async_sleep'); if (results[0] != "nextTick") { throw new Error(`expected nextTick, got: ${results[0]}`); } if (results[1] != "macrotask") { throw new Error(`expected macrotask, got: ${results[1]}`); } })(); "#, ) .unwrap(); runtime.run_event_loop(false).await.unwrap(); } #[tokio::test] async fn test_set_macrotask_callback_set_next_tick_callback_multiple() { let mut runtime = JsRuntime::new(Default::default()); runtime .execute_script( "multiple_macrotasks_and_nextticks.js", r#" Deno.core.ops.op_set_macrotask_callback(() => { return true; }); Deno.core.ops.op_set_macrotask_callback(() => { return true; }); Deno.core.ops.op_set_next_tick_callback(() => {}); Deno.core.ops.op_set_next_tick_callback(() => {}); "#, ) .unwrap(); let isolate = runtime.v8_isolate(); let state_rc = JsRuntime::state(isolate); let state = state_rc.borrow(); assert_eq!(state.js_macrotask_cbs.len(), 2); assert_eq!(state.js_nexttick_cbs.len(), 2); } #[test] fn test_has_tick_scheduled() { use futures::task::ArcWake; static MACROTASK: AtomicUsize = AtomicUsize::new(0); static NEXT_TICK: AtomicUsize = AtomicUsize::new(0); #[op] fn op_macrotask() -> Result<(), AnyError> { MACROTASK.fetch_add(1, Ordering::Relaxed); Ok(()) } #[op] fn op_next_tick() -> Result<(), AnyError> { NEXT_TICK.fetch_add(1, Ordering::Relaxed); Ok(()) } let extension = Extension::builder() .ops(vec![op_macrotask::decl(), op_next_tick::decl()]) .build(); let mut runtime = JsRuntime::new(RuntimeOptions { extensions: vec![extension], ..Default::default() }); runtime .execute_script( "has_tick_scheduled.js", r#" Deno.core.ops.op_set_macrotask_callback(() => { Deno.core.ops.op_macrotask(); return true; // We're done. }); Deno.core.ops.op_set_next_tick_callback(() => Deno.core.ops.op_next_tick()); Deno.core.ops.op_set_has_tick_scheduled(true); "#, ) .unwrap(); struct ArcWakeImpl(Arc<AtomicUsize>); impl ArcWake for ArcWakeImpl { fn wake_by_ref(arc_self: &Arc<Self>) { arc_self.0.fetch_add(1, Ordering::Relaxed); } } let awoken_times = Arc::new(AtomicUsize::new(0)); let waker = futures::task::waker(Arc::new(ArcWakeImpl(awoken_times.clone()))); let cx = &mut Context::from_waker(&waker); assert!(matches!(runtime.poll_event_loop(cx, false), Poll::Pending)); assert_eq!(1, MACROTASK.load(Ordering::Relaxed)); assert_eq!(1, NEXT_TICK.load(Ordering::Relaxed)); assert_eq!(awoken_times.swap(0, Ordering::Relaxed), 1); assert!(matches!(runtime.poll_event_loop(cx, false), Poll::Pending)); assert_eq!(awoken_times.swap(0, Ordering::Relaxed), 1); assert!(matches!(runtime.poll_event_loop(cx, false), Poll::Pending)); assert_eq!(awoken_times.swap(0, Ordering::Relaxed), 1); assert!(matches!(runtime.poll_event_loop(cx, false), Poll::Pending)); assert_eq!(awoken_times.swap(0, Ordering::Relaxed), 1); let state_rc = JsRuntime::state(runtime.v8_isolate()); state_rc.borrow_mut().has_tick_scheduled = false; assert!(matches!( runtime.poll_event_loop(cx, false), Poll::Ready(Ok(())) )); assert_eq!(awoken_times.load(Ordering::Relaxed), 0); assert!(matches!( runtime.poll_event_loop(cx, false), Poll::Ready(Ok(())) )); assert_eq!(awoken_times.load(Ordering::Relaxed), 0); } #[test] fn terminate_during_module_eval() { #[derive(Default)] struct ModsLoader; impl ModuleLoader for ModsLoader { fn resolve( &self, specifier: &str, referrer: &str, _is_main: bool, ) -> Result<ModuleSpecifier, Error> { assert_eq!(specifier, "file:///main.js"); assert_eq!(referrer, "."); let s = crate::resolve_import(specifier, referrer).unwrap(); Ok(s) } fn load( &self, _module_specifier: &ModuleSpecifier, _maybe_referrer: Option<ModuleSpecifier>, _is_dyn_import: bool, ) -> Pin<Box<ModuleSourceFuture>> { async move { Ok(ModuleSource { code: b"console.log('hello world');".to_vec().into_boxed_slice(), module_url_specified: "file:///main.js".to_string(), module_url_found: "file:///main.js".to_string(), module_type: ModuleType::JavaScript, }) } .boxed_local() } } let loader = std::rc::Rc::new(ModsLoader::default()); let mut runtime = JsRuntime::new(RuntimeOptions { module_loader: Some(loader), ..Default::default() }); let specifier = crate::resolve_url("file:///main.js").unwrap(); let source_code = "Deno.core.print('hello\\n')".to_string(); let module_id = futures::executor::block_on( runtime.load_main_module(&specifier, Some(source_code)), ) .unwrap(); runtime.v8_isolate().terminate_execution(); let mod_result = futures::executor::block_on(runtime.mod_evaluate(module_id)).unwrap(); assert!(mod_result .unwrap_err() .to_string() .contains("JavaScript execution has been terminated")); } #[tokio::test] async fn test_set_promise_reject_callback() { static PROMISE_REJECT: AtomicUsize = AtomicUsize::new(0); #[op] fn op_promise_reject() -> Result<(), AnyError> { PROMISE_REJECT.fetch_add(1, Ordering::Relaxed); Ok(()) } let extension = Extension::builder() .ops(vec![op_promise_reject::decl()]) .build(); let mut runtime = JsRuntime::new(RuntimeOptions { extensions: vec![extension], ..Default::default() }); runtime .execute_script( "promise_reject_callback.js", r#" // Note: |promise| is not the promise created below, it's a child. Deno.core.ops.op_set_promise_reject_callback((type, promise, reason) => { if (type !== /* PromiseRejectWithNoHandler */ 0) { throw Error("unexpected type: " + type); } if (reason.message !== "reject") { throw Error("unexpected reason: " + reason); } Deno.core.ops.op_store_pending_promise_exception(promise); Deno.core.ops.op_promise_reject(); }); new Promise((_, reject) => reject(Error("reject"))); "#, ) .unwrap(); runtime.run_event_loop(false).await.unwrap_err(); assert_eq!(1, PROMISE_REJECT.load(Ordering::Relaxed)); runtime .execute_script( "promise_reject_callback.js", r#" { const prev = Deno.core.ops.op_set_promise_reject_callback((...args) => { prev(...args); }); } new Promise((_, reject) => reject(Error("reject"))); "#, ) .unwrap(); runtime.run_event_loop(false).await.unwrap_err(); assert_eq!(2, PROMISE_REJECT.load(Ordering::Relaxed)); } #[tokio::test] async fn test_set_promise_reject_callback_top_level_await() { static PROMISE_REJECT: AtomicUsize = AtomicUsize::new(0); #[op] fn op_promise_reject() -> Result<(), AnyError> { PROMISE_REJECT.fetch_add(1, Ordering::Relaxed); Ok(()) } let extension = Extension::builder() .ops(vec![op_promise_reject::decl()]) .build(); #[derive(Default)] struct ModsLoader; impl ModuleLoader for ModsLoader { fn resolve( &self, specifier: &str, referrer: &str, _is_main: bool, ) -> Result<ModuleSpecifier, Error> { assert_eq!(specifier, "file:///main.js"); assert_eq!(referrer, "."); let s = crate::resolve_import(specifier, referrer).unwrap(); Ok(s) } fn load( &self, _module_specifier: &ModuleSpecifier, _maybe_referrer: Option<ModuleSpecifier>, _is_dyn_import: bool, ) -> Pin<Box<ModuleSourceFuture>> { let source = r#" Deno.core.ops.op_set_promise_reject_callback((type, promise, reason) => { Deno.core.ops.op_promise_reject(); }); throw new Error('top level throw'); "#; async move { Ok(ModuleSource { code: source.as_bytes().to_vec().into_boxed_slice(), module_url_specified: "file:///main.js".to_string(), module_url_found: "file:///main.js".to_string(), module_type: ModuleType::JavaScript, }) } .boxed_local() } } let mut runtime = JsRuntime::new(RuntimeOptions { extensions: vec![extension], module_loader: Some(Rc::new(ModsLoader)), ..Default::default() }); let id = runtime .load_main_module(&crate::resolve_url("file:///main.js").unwrap(), None) .await .unwrap(); let receiver = runtime.mod_evaluate(id); runtime.run_event_loop(false).await.unwrap(); receiver.await.unwrap().unwrap_err(); assert_eq!(1, PROMISE_REJECT.load(Ordering::Relaxed)); } #[test] fn test_op_return_serde_v8_error() { #[op] fn op_err() -> Result<std::collections::BTreeMap<u64, u64>, anyhow::Error> { Ok([(1, 2), (3, 4)].into_iter().collect()) // Maps can't have non-string keys in serde_v8 } let ext = Extension::builder().ops(vec![op_err::decl()]).build(); let mut runtime = JsRuntime::new(RuntimeOptions { extensions: vec![ext], ..Default::default() }); assert!(runtime .execute_script( "test_op_return_serde_v8_error.js", "Deno.core.ops.op_err()" ) .is_err()); } #[test] fn test_op_high_arity() { #[op] fn op_add_4( x1: i64, x2: i64, x3: i64, x4: i64, ) -> Result<i64, anyhow::Error> { Ok(x1 + x2 + x3 + x4) } let ext = Extension::builder().ops(vec![op_add_4::decl()]).build(); let mut runtime = JsRuntime::new(RuntimeOptions { extensions: vec![ext], ..Default::default() }); let r = runtime .execute_script("test.js", "Deno.core.ops.op_add_4(1, 2, 3, 4)") .unwrap(); let scope = &mut runtime.handle_scope(); assert_eq!(r.open(scope).integer_value(scope), Some(10)); } #[test] fn test_op_disabled() { #[op] fn op_foo() -> Result<i64, anyhow::Error> { Ok(42) } let ext = Extension::builder() .ops(vec![op_foo::decl().disable()]) .build(); let mut runtime = JsRuntime::new(RuntimeOptions { extensions: vec![ext], ..Default::default() }); let r = runtime .execute_script("test.js", "Deno.core.ops.op_foo()") .unwrap(); let scope = &mut runtime.handle_scope(); assert!(r.open(scope).is_undefined()); } #[test] fn test_op_detached_buffer() { use serde_v8::DetachedBuffer; #[op] fn op_sum_take(b: DetachedBuffer) -> Result<u64, anyhow::Error> { Ok(b.as_ref().iter().clone().map(|x| *x as u64).sum()) } #[op] fn op_boomerang( b: DetachedBuffer, ) -> Result<DetachedBuffer, anyhow::Error> { Ok(b) } let ext = Extension::builder() .ops(vec![op_sum_take::decl(), op_boomerang::decl()]) .build(); let mut runtime = JsRuntime::new(RuntimeOptions { extensions: vec![ext], ..Default::default() }); runtime .execute_script( "test.js", r#" const a1 = new Uint8Array([1,2,3]); const a1b = a1.subarray(0, 3); const a2 = new Uint8Array([5,10,15]); const a2b = a2.subarray(0, 3); if (!(a1.length > 0 && a1b.length > 0)) { throw new Error("a1 & a1b should have a length"); } let sum = Deno.core.ops.op_sum_take(a1b); if (sum !== 6) { throw new Error(`Bad sum: ${sum}`); } if (a1.length > 0 || a1b.length > 0) { throw new Error("expecting a1 & a1b to be detached"); } const a3 = Deno.core.ops.op_boomerang(a2b); if (a3.byteLength != 3) { throw new Error(`Expected a3.byteLength === 3, got ${a3.byteLength}`); } if (a3[0] !== 5 || a3[1] !== 10) { throw new Error(`Invalid a3: ${a3[0]}, ${a3[1]}`); } if (a2.byteLength > 0 || a2b.byteLength > 0) { throw new Error("expecting a2 & a2b to be detached, a3 re-attached"); } const wmem = new WebAssembly.Memory({ initial: 1, maximum: 2 }); const w32 = new Uint32Array(wmem.buffer); w32[0] = 1; w32[1] = 2; w32[2] = 3; const assertWasmThrow = (() => { try { let sum = Deno.core.ops.op_sum_take(w32.subarray(0, 2)); return false; } catch(e) { return e.message.includes('ExpectedDetachable'); } }); if (!assertWasmThrow()) { throw new Error("expected wasm mem to not be detachable"); } "#, ) .unwrap(); } #[test] fn test_op_unstable_disabling() { #[op] fn op_foo() -> Result<i64, anyhow::Error> { Ok(42) } #[op(unstable)] fn op_bar() -> Result<i64, anyhow::Error> { Ok(42) } let ext = Extension::builder() .ops(vec![op_foo::decl(), op_bar::decl()]) .middleware(|op| if op.is_unstable { op.disable() } else { op }) .build(); let mut runtime = JsRuntime::new(RuntimeOptions { extensions: vec![ext], ..Default::default() }); runtime .execute_script( "test.js", r#" if (Deno.core.ops.op_foo() !== 42) { throw new Error("Exptected op_foo() === 42"); } if (Deno.core.ops.op_bar() !== undefined) { throw new Error("Expected op_bar to be disabled") } "#, ) .unwrap(); } #[test] fn js_realm_simple() { let mut runtime = JsRuntime::new(Default::default()); let main_context = runtime.global_context(); let main_global = { let scope = &mut runtime.handle_scope(); let local_global = main_context.open(scope).global(scope); v8::Global::new(scope, local_global) }; let realm = runtime.create_realm().unwrap(); assert_ne!(realm.context(), &main_context); assert_ne!(realm.global_object(runtime.v8_isolate()), main_global); let main_object = runtime.execute_script("", "Object").unwrap(); let realm_object = realm .execute_script(runtime.v8_isolate(), "", "Object") .unwrap(); assert_ne!(main_object, realm_object); } #[test] fn js_realm_init() { #[op] fn op_test() -> Result<String, Error> { Ok(String::from("Test")) } let mut runtime = JsRuntime::new(RuntimeOptions { extensions: vec![Extension::builder().ops(vec![op_test::decl()]).build()], ..Default::default() }); let realm = runtime.create_realm().unwrap(); let ret = realm .execute_script(runtime.v8_isolate(), "", "Deno.core.ops.op_test()") .unwrap(); let scope = &mut realm.handle_scope(runtime.v8_isolate()); assert_eq!(ret, serde_v8::to_v8(scope, "Test").unwrap()); } #[test] fn js_realm_init_snapshot() { let snapshot = { let runtime = JsRuntime::new(RuntimeOptions { will_snapshot: true, ..Default::default() }); let snap: &[u8] = &runtime.snapshot(); Vec::from(snap).into_boxed_slice() }; #[op] fn op_test() -> Result<String, Error> { Ok(String::from("Test")) } let mut runtime = JsRuntime::new(RuntimeOptions { startup_snapshot: Some(Snapshot::Boxed(snapshot)), extensions: vec![Extension::builder().ops(vec![op_test::decl()]).build()], ..Default::default() }); let realm = runtime.create_realm().unwrap(); let ret = realm .execute_script(runtime.v8_isolate(), "", "Deno.core.ops.op_test()") .unwrap(); let scope = &mut realm.handle_scope(runtime.v8_isolate()); assert_eq!(ret, serde_v8::to_v8(scope, "Test").unwrap()); } #[test] fn test_array_by_copy() { // Verify that "array by copy" proposal is enabled (https://github.com/tc39/proposal-change-array-by-copy) let mut runtime = JsRuntime::new(Default::default()); assert!(runtime .execute_script( "test_array_by_copy.js", "const a = [1, 2, 3]; const b = a.toReversed(); if (!(a[0] === 1 && a[1] === 2 && a[2] === 3)) { throw new Error('Expected a to be intact'); } if (!(b[0] === 3 && b[1] === 2 && b[2] === 1)) { throw new Error('Expected b to be reversed'); }", ) .is_ok()); }}
Version Info