diff options
Diffstat (limited to 'runtime')
38 files changed, 4900 insertions, 1147 deletions
diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml index ba9dc6243..b59cd14fa 100644 --- a/runtime/Cargo.toml +++ b/runtime/Cargo.toml @@ -2,7 +2,7 @@ [package] name = "deno_runtime" -version = "0.180.0" +version = "0.186.0" authors.workspace = true edition.workspace = true license.workspace = true @@ -100,6 +100,8 @@ deno_websocket.workspace = true deno_webstorage.workspace = true node_resolver = { workspace = true, features = ["sync"] } +async-trait.workspace = true +color-print.workspace = true dlopen2.workspace = true encoding_rs.workspace = true fastwebsockets.workspace = true @@ -113,13 +115,21 @@ log.workspace = true netif = "0.1.6" notify.workspace = true once_cell.workspace = true +opentelemetry.workspace = true +opentelemetry-http.workspace = true +opentelemetry-otlp.workspace = true +opentelemetry-semantic-conventions.workspace = true +opentelemetry_sdk.workspace = true percent-encoding.workspace = true +pin-project.workspace = true regex.workspace = true rustyline = { workspace = true, features = ["custom-bindings"] } +same-file = "1.0.6" serde.workspace = true signal-hook = "0.3.17" signal-hook-registry = "1.4.0" tempfile.workspace = true +thiserror.workspace = true tokio.workspace = true tokio-metrics.workspace = true twox-hash.workspace = true diff --git a/runtime/clippy.toml b/runtime/clippy.toml index 53676a90e..79e6bbd08 100644 --- a/runtime/clippy.toml +++ b/runtime/clippy.toml @@ -42,4 +42,5 @@ disallowed-methods = [ { path = "std::fs::write", reason = "File system operations should be done using FileSystem trait" }, { path = "std::path::Path::canonicalize", reason = "File system operations should be done using FileSystem trait" }, { path = "std::path::Path::exists", reason = "File system operations should be done using FileSystem trait" }, + { path = "std::process::exit", reason = "use deno_runtime::exit instead" }, ] diff --git a/runtime/code_cache.rs b/runtime/code_cache.rs index 2a56543a4..b4a7ce188 100644 --- a/runtime/code_cache.rs +++ b/runtime/code_cache.rs @@ -2,20 +2,12 @@ use deno_core::ModuleSpecifier; +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum CodeCacheType { EsModule, Script, } -impl CodeCacheType { - pub fn as_str(&self) -> &str { - match self { - Self::EsModule => "esmodule", - Self::Script => "script", - } - } -} - pub trait CodeCache: Send + Sync { fn get_sync( &self, @@ -23,6 +15,7 @@ pub trait CodeCache: Send + Sync { code_cache_type: CodeCacheType, source_hash: u64, ) -> Option<Vec<u8>>; + fn set_sync( &self, specifier: ModuleSpecifier, diff --git a/runtime/errors.rs b/runtime/errors.rs index 4c6aeab98..4268fbd50 100644 --- a/runtime/errors.rs +++ b/runtime/errors.rs @@ -9,6 +9,16 @@ //! Diagnostics are compile-time type errors, whereas JsErrors are runtime //! exceptions. +use crate::ops::fs_events::FsEventsError; +use crate::ops::http::HttpStartError; +use crate::ops::os::OsError; +use crate::ops::permissions::PermissionError; +use crate::ops::process::CheckRunPermissionError; +use crate::ops::process::ProcessError; +use crate::ops::signal::SignalError; +use crate::ops::tty::TtyError; +use crate::ops::web_worker::SyncFetchError; +use crate::ops::worker_host::CreateWorkerError; use deno_broadcast_channel::BroadcastChannelError; use deno_cache::CacheError; use deno_canvas::CanvasError; @@ -17,12 +27,100 @@ use deno_core::serde_json; use deno_core::url; use deno_core::ModuleResolutionError; use deno_cron::CronError; +use deno_crypto::DecryptError; +use deno_crypto::EncryptError; +use deno_crypto::ExportKeyError; +use deno_crypto::GenerateKeyError; +use deno_crypto::ImportKeyError; +use deno_fetch::FetchError; +use deno_fetch::HttpClientCreateError; +use deno_ffi::CallError; +use deno_ffi::CallbackError; +use deno_ffi::DlfcnError; +use deno_ffi::IRError; +use deno_ffi::ReprError; +use deno_ffi::StaticError; +use deno_fs::FsOpsError; +use deno_fs::FsOpsErrorKind; +use deno_http::HttpError; +use deno_http::HttpNextError; +use deno_http::WebSocketUpgradeError; +use deno_io::fs::FsError; +use deno_kv::KvCheckError; +use deno_kv::KvError; +use deno_kv::KvErrorKind; +use deno_kv::KvMutationError; +use deno_napi::NApiError; +use deno_net::ops::NetError; +use deno_permissions::ChildPermissionError; +use deno_permissions::NetDescriptorFromUrlParseError; +use deno_permissions::PathResolveError; +use deno_permissions::PermissionCheckError; +use deno_permissions::RunDescriptorParseError; +use deno_permissions::SysDescriptorParseError; use deno_tls::TlsError; +use deno_web::BlobError; +use deno_web::CompressionError; +use deno_web::MessagePortError; +use deno_web::StreamResourceError; +use deno_web::WebError; +use deno_websocket::HandshakeError; +use deno_websocket::WebsocketError; +use deno_webstorage::WebStorageError; +use rustyline::error::ReadlineError; use std::env; use std::error::Error; use std::io; use std::sync::Arc; +fn get_run_descriptor_parse_error(e: &RunDescriptorParseError) -> &'static str { + match e { + RunDescriptorParseError::Which(_) => "Error", + RunDescriptorParseError::PathResolve(e) => get_path_resolve_error(e), + RunDescriptorParseError::EmptyRunQuery => "Error", + } +} + +fn get_sys_descriptor_parse_error(e: &SysDescriptorParseError) -> &'static str { + match e { + SysDescriptorParseError::InvalidKind(_) => "TypeError", + SysDescriptorParseError::Empty => "Error", + } +} + +fn get_path_resolve_error(e: &PathResolveError) -> &'static str { + match e { + PathResolveError::CwdResolve(e) => get_io_error_class(e), + PathResolveError::EmptyPath => "Error", + } +} + +fn get_permission_error_class(e: &PermissionError) -> &'static str { + match e { + PermissionError::InvalidPermissionName(_) => "ReferenceError", + PermissionError::PathResolve(e) => get_path_resolve_error(e), + PermissionError::NetDescriptorParse(_) => "URIError", + PermissionError::SysDescriptorParse(e) => get_sys_descriptor_parse_error(e), + PermissionError::RunDescriptorParse(e) => get_run_descriptor_parse_error(e), + } +} + +fn get_permission_check_error_class(e: &PermissionCheckError) -> &'static str { + match e { + PermissionCheckError::PermissionDenied(_) => "NotCapable", + PermissionCheckError::InvalidFilePath(_) => "URIError", + PermissionCheckError::NetDescriptorForUrlParse(e) => match e { + NetDescriptorFromUrlParseError::MissingHost(_) => "TypeError", + NetDescriptorFromUrlParseError::Host(_) => "URIError", + }, + PermissionCheckError::SysDescriptorParse(e) => { + get_sys_descriptor_parse_error(e) + } + PermissionCheckError::PathResolve(e) => get_path_resolve_error(e), + PermissionCheckError::HostParse(_) => "URIError", + } +} + fn get_dlopen_error_class(error: &dlopen2::Error) -> &'static str { use dlopen2::Error::*; match error { @@ -158,6 +256,381 @@ pub fn get_nix_error_class(error: &nix::Error) -> &'static str { } } +fn get_webgpu_error_class(e: &deno_webgpu::InitError) -> &'static str { + match e { + deno_webgpu::InitError::Resource(e) => { + get_error_class_name(e).unwrap_or("Error") + } + deno_webgpu::InitError::InvalidAdapter(_) => "Error", + deno_webgpu::InitError::RequestDevice(_) => "DOMExceptionOperationError", + deno_webgpu::InitError::InvalidDevice(_) => "Error", + } +} + +fn get_webgpu_buffer_error_class( + e: &deno_webgpu::buffer::BufferError, +) -> &'static str { + match e { + deno_webgpu::buffer::BufferError::Resource(e) => { + get_error_class_name(e).unwrap_or("Error") + } + deno_webgpu::buffer::BufferError::InvalidUsage => "TypeError", + deno_webgpu::buffer::BufferError::Access(_) => "DOMExceptionOperationError", + } +} + +fn get_webgpu_bundle_error_class( + e: &deno_webgpu::bundle::BundleError, +) -> &'static str { + match e { + deno_webgpu::bundle::BundleError::Resource(e) => { + get_error_class_name(e).unwrap_or("Error") + } + deno_webgpu::bundle::BundleError::InvalidSize => "TypeError", + } +} + +fn get_webgpu_byow_error_class( + e: &deno_webgpu::byow::ByowError, +) -> &'static str { + match e { + deno_webgpu::byow::ByowError::WebGPUNotInitiated => "TypeError", + deno_webgpu::byow::ByowError::InvalidParameters => "TypeError", + deno_webgpu::byow::ByowError::CreateSurface(_) => "Error", + deno_webgpu::byow::ByowError::InvalidSystem => "TypeError", + #[cfg(any( + target_os = "windows", + target_os = "linux", + target_os = "freebsd", + target_os = "openbsd" + ))] + deno_webgpu::byow::ByowError::NullWindow => "TypeError", + #[cfg(any( + target_os = "linux", + target_os = "freebsd", + target_os = "openbsd" + ))] + deno_webgpu::byow::ByowError::NullDisplay => "TypeError", + #[cfg(target_os = "macos")] + deno_webgpu::byow::ByowError::NSViewDisplay => "TypeError", + } +} + +fn get_webgpu_render_pass_error_class( + e: &deno_webgpu::render_pass::RenderPassError, +) -> &'static str { + match e { + deno_webgpu::render_pass::RenderPassError::Resource(e) => { + get_error_class_name(e).unwrap_or("Error") + } + deno_webgpu::render_pass::RenderPassError::InvalidSize => "TypeError", + } +} + +fn get_webgpu_surface_error_class( + e: &deno_webgpu::surface::SurfaceError, +) -> &'static str { + match e { + deno_webgpu::surface::SurfaceError::Resource(e) => { + get_error_class_name(e).unwrap_or("Error") + } + deno_webgpu::surface::SurfaceError::Surface(_) => "Error", + deno_webgpu::surface::SurfaceError::InvalidStatus => "Error", + } +} + +fn get_crypto_decrypt_error_class(e: &DecryptError) -> &'static str { + match e { + DecryptError::General(e) => get_crypto_shared_error_class(e), + DecryptError::Pkcs1(_) => "Error", + DecryptError::Failed => "DOMExceptionOperationError", + DecryptError::InvalidLength => "TypeError", + DecryptError::InvalidCounterLength => "TypeError", + DecryptError::InvalidTagLength => "TypeError", + DecryptError::InvalidKeyOrIv => "DOMExceptionOperationError", + DecryptError::TooMuchData => "DOMExceptionOperationError", + DecryptError::InvalidIvLength => "TypeError", + DecryptError::Rsa(_) => "DOMExceptionOperationError", + } +} + +fn get_crypto_encrypt_error_class(e: &EncryptError) -> &'static str { + match e { + EncryptError::General(e) => get_crypto_shared_error_class(e), + EncryptError::InvalidKeyOrIv => "DOMExceptionOperationError", + EncryptError::Failed => "DOMExceptionOperationError", + EncryptError::InvalidLength => "TypeError", + EncryptError::InvalidIvLength => "TypeError", + EncryptError::InvalidCounterLength => "TypeError", + EncryptError::TooMuchData => "DOMExceptionOperationError", + } +} + +fn get_crypto_shared_error_class(e: &deno_crypto::SharedError) -> &'static str { + match e { + deno_crypto::SharedError::ExpectedValidPrivateKey => "TypeError", + deno_crypto::SharedError::ExpectedValidPublicKey => "TypeError", + deno_crypto::SharedError::ExpectedValidPrivateECKey => "TypeError", + deno_crypto::SharedError::ExpectedValidPublicECKey => "TypeError", + deno_crypto::SharedError::ExpectedPrivateKey => "TypeError", + deno_crypto::SharedError::ExpectedPublicKey => "TypeError", + deno_crypto::SharedError::ExpectedSecretKey => "TypeError", + deno_crypto::SharedError::FailedDecodePrivateKey => { + "DOMExceptionOperationError" + } + deno_crypto::SharedError::FailedDecodePublicKey => { + "DOMExceptionOperationError" + } + deno_crypto::SharedError::UnsupportedFormat => { + "DOMExceptionNotSupportedError" + } + } +} + +fn get_crypto_ed25519_error_class( + e: &deno_crypto::Ed25519Error, +) -> &'static str { + match e { + deno_crypto::Ed25519Error::FailedExport => "DOMExceptionOperationError", + deno_crypto::Ed25519Error::Der(_) => "Error", + deno_crypto::Ed25519Error::KeyRejected(_) => "Error", + } +} + +fn get_crypto_export_key_error_class(e: &ExportKeyError) -> &'static str { + match e { + ExportKeyError::General(e) => get_crypto_shared_error_class(e), + ExportKeyError::Der(_) => "Error", + ExportKeyError::UnsupportedNamedCurve => "DOMExceptionNotSupportedError", + } +} + +fn get_crypto_generate_key_error_class(e: &GenerateKeyError) -> &'static str { + match e { + GenerateKeyError::General(e) => get_crypto_shared_error_class(e), + GenerateKeyError::BadPublicExponent => "DOMExceptionOperationError", + GenerateKeyError::InvalidHMACKeyLength => "DOMExceptionOperationError", + GenerateKeyError::FailedRSAKeySerialization => "DOMExceptionOperationError", + GenerateKeyError::InvalidAESKeyLength => "DOMExceptionOperationError", + GenerateKeyError::FailedRSAKeyGeneration => "DOMExceptionOperationError", + GenerateKeyError::FailedECKeyGeneration => "DOMExceptionOperationError", + GenerateKeyError::FailedKeyGeneration => "DOMExceptionOperationError", + } +} + +fn get_crypto_import_key_error_class(e: &ImportKeyError) -> &'static str { + match e { + ImportKeyError::General(e) => get_crypto_shared_error_class(e), + ImportKeyError::InvalidModulus => "DOMExceptionDataError", + ImportKeyError::InvalidPublicExponent => "DOMExceptionDataError", + ImportKeyError::InvalidPrivateExponent => "DOMExceptionDataError", + ImportKeyError::InvalidFirstPrimeFactor => "DOMExceptionDataError", + ImportKeyError::InvalidSecondPrimeFactor => "DOMExceptionDataError", + ImportKeyError::InvalidFirstCRTExponent => "DOMExceptionDataError", + ImportKeyError::InvalidSecondCRTExponent => "DOMExceptionDataError", + ImportKeyError::InvalidCRTCoefficient => "DOMExceptionDataError", + ImportKeyError::InvalidB64Coordinate => "DOMExceptionDataError", + ImportKeyError::InvalidRSAPublicKey => "DOMExceptionDataError", + ImportKeyError::InvalidRSAPrivateKey => "DOMExceptionDataError", + ImportKeyError::UnsupportedAlgorithm => "DOMExceptionDataError", + ImportKeyError::PublicKeyTooLong => "DOMExceptionDataError", + ImportKeyError::PrivateKeyTooLong => "DOMExceptionDataError", + ImportKeyError::InvalidP256ECPoint => "DOMExceptionDataError", + ImportKeyError::InvalidP384ECPoint => "DOMExceptionDataError", + ImportKeyError::InvalidP521ECPoint => "DOMExceptionDataError", + ImportKeyError::UnsupportedNamedCurve => "DOMExceptionDataError", + ImportKeyError::CurveMismatch => "DOMExceptionDataError", + ImportKeyError::InvalidKeyData => "DOMExceptionDataError", + ImportKeyError::InvalidJWKPrivateKey => "DOMExceptionDataError", + ImportKeyError::EllipticCurve(_) => "DOMExceptionDataError", + ImportKeyError::ExpectedValidPkcs8Data => "DOMExceptionDataError", + ImportKeyError::MalformedParameters => "DOMExceptionDataError", + ImportKeyError::Spki(_) => "DOMExceptionDataError", + ImportKeyError::InvalidP256ECSPKIData => "DOMExceptionDataError", + ImportKeyError::InvalidP384ECSPKIData => "DOMExceptionDataError", + ImportKeyError::InvalidP521ECSPKIData => "DOMExceptionDataError", + ImportKeyError::Der(_) => "DOMExceptionDataError", + } +} + +fn get_crypto_x448_error_class(e: &deno_crypto::X448Error) -> &'static str { + match e { + deno_crypto::X448Error::FailedExport => "DOMExceptionOperationError", + deno_crypto::X448Error::Der(_) => "Error", + } +} + +fn get_crypto_x25519_error_class(e: &deno_crypto::X25519Error) -> &'static str { + match e { + deno_crypto::X25519Error::FailedExport => "DOMExceptionOperationError", + deno_crypto::X25519Error::Der(_) => "Error", + } +} + +fn get_crypto_error_class(e: &deno_crypto::Error) -> &'static str { + match e { + deno_crypto::Error::Der(_) => "Error", + deno_crypto::Error::JoinError(_) => "Error", + deno_crypto::Error::MissingArgumentHash => "TypeError", + deno_crypto::Error::MissingArgumentSaltLength => "TypeError", + deno_crypto::Error::Other(e) => get_error_class_name(e).unwrap_or("Error"), + deno_crypto::Error::UnsupportedAlgorithm => "TypeError", + deno_crypto::Error::KeyRejected(_) => "Error", + deno_crypto::Error::RSA(_) => "Error", + deno_crypto::Error::Pkcs1(_) => "Error", + deno_crypto::Error::Unspecified(_) => "Error", + deno_crypto::Error::InvalidKeyFormat => "TypeError", + deno_crypto::Error::MissingArgumentPublicKey => "TypeError", + deno_crypto::Error::P256Ecdsa(_) => "Error", + deno_crypto::Error::DecodePrivateKey => "TypeError", + deno_crypto::Error::MissingArgumentNamedCurve => "TypeError", + deno_crypto::Error::MissingArgumentInfo => "TypeError", + deno_crypto::Error::HKDFLengthTooLarge => "DOMExceptionOperationError", + deno_crypto::Error::General(e) => get_crypto_shared_error_class(e), + deno_crypto::Error::Base64Decode(_) => "Error", + deno_crypto::Error::DataInvalidSize => "TypeError", + deno_crypto::Error::InvalidKeyLength => "TypeError", + deno_crypto::Error::EncryptionError => "DOMExceptionOperationError", + deno_crypto::Error::DecryptionError => "DOMExceptionOperationError", + deno_crypto::Error::ArrayBufferViewLengthExceeded(_) => { + "DOMExceptionQuotaExceededError" + } + } +} + +fn get_napi_error_class(e: &NApiError) -> &'static str { + match e { + NApiError::InvalidPath + | NApiError::LibLoading(_) + | NApiError::ModuleNotFound(_) => "TypeError", + NApiError::Permission(e) => get_permission_check_error_class(e), + } +} + +fn get_web_error_class(e: &WebError) -> &'static str { + match e { + WebError::Base64Decode => "DOMExceptionInvalidCharacterError", + WebError::InvalidEncodingLabel(_) => "RangeError", + WebError::BufferTooLong => "TypeError", + WebError::ValueTooLarge => "RangeError", + WebError::BufferTooSmall => "RangeError", + WebError::DataInvalid => "TypeError", + WebError::DataError(_) => "Error", + } +} + +fn get_web_compression_error_class(e: &CompressionError) -> &'static str { + match e { + CompressionError::UnsupportedFormat => "TypeError", + CompressionError::ResourceClosed => "TypeError", + CompressionError::IoTypeError(_) => "TypeError", + CompressionError::Io(e) => get_io_error_class(e), + } +} + +fn get_web_message_port_error_class(e: &MessagePortError) -> &'static str { + match e { + MessagePortError::InvalidTransfer => "TypeError", + MessagePortError::NotReady => "TypeError", + MessagePortError::TransferSelf => "TypeError", + MessagePortError::Canceled(e) => { + let io_err: io::Error = e.to_owned().into(); + get_io_error_class(&io_err) + } + MessagePortError::Resource(e) => get_error_class_name(e).unwrap_or("Error"), + } +} + +fn get_web_stream_resource_error_class( + e: &StreamResourceError, +) -> &'static str { + match e { + StreamResourceError::Canceled(e) => { + let io_err: io::Error = e.to_owned().into(); + get_io_error_class(&io_err) + } + StreamResourceError::Js(_) => "TypeError", + } +} + +fn get_web_blob_error_class(e: &BlobError) -> &'static str { + match e { + BlobError::BlobPartNotFound => "TypeError", + BlobError::SizeLargerThanBlobPart => "TypeError", + BlobError::BlobURLsNotSupported => "TypeError", + BlobError::Url(_) => "Error", + } +} + +fn get_ffi_repr_error_class(e: &ReprError) -> &'static str { + match e { + ReprError::InvalidOffset => "TypeError", + ReprError::InvalidArrayBuffer => "TypeError", + ReprError::DestinationLengthTooShort => "RangeError", + ReprError::InvalidCString => "TypeError", + ReprError::CStringTooLong => "TypeError", + ReprError::InvalidBool => "TypeError", + ReprError::InvalidU8 => "TypeError", + ReprError::InvalidI8 => "TypeError", + ReprError::InvalidU16 => "TypeError", + ReprError::InvalidI16 => "TypeError", + ReprError::InvalidU32 => "TypeError", + ReprError::InvalidI32 => "TypeError", + ReprError::InvalidU64 => "TypeError", + ReprError::InvalidI64 => "TypeError", + ReprError::InvalidF32 => "TypeError", + ReprError::InvalidF64 => "TypeError", + ReprError::InvalidPointer => "TypeError", + ReprError::Permission(e) => get_permission_check_error_class(e), + } +} + +fn get_ffi_dlfcn_error_class(e: &DlfcnError) -> &'static str { + match e { + DlfcnError::RegisterSymbol { .. } => "Error", + DlfcnError::Dlopen(_) => "Error", + DlfcnError::Permission(e) => get_permission_check_error_class(e), + DlfcnError::Other(e) => get_error_class_name(e).unwrap_or("Error"), + } +} + +fn get_ffi_static_error_class(e: &StaticError) -> &'static str { + match e { + StaticError::Dlfcn(e) => get_ffi_dlfcn_error_class(e), + StaticError::InvalidTypeVoid => "TypeError", + StaticError::InvalidTypeStruct => "TypeError", + StaticError::Resource(e) => get_error_class_name(e).unwrap_or("Error"), + } +} + +fn get_ffi_callback_error_class(e: &CallbackError) -> &'static str { + match e { + CallbackError::Resource(e) => get_error_class_name(e).unwrap_or("Error"), + CallbackError::Other(e) => get_error_class_name(e).unwrap_or("Error"), + CallbackError::Permission(e) => get_permission_check_error_class(e), + } +} + +fn get_ffi_call_error_class(e: &CallError) -> &'static str { + match e { + CallError::IR(_) => "TypeError", + CallError::NonblockingCallFailure(_) => "Error", + CallError::InvalidSymbol(_) => "TypeError", + CallError::Permission(e) => get_permission_check_error_class(e), + CallError::Callback(e) => get_ffi_callback_error_class(e), + CallError::Resource(e) => get_error_class_name(e).unwrap_or("Error"), + } +} + +fn get_webstorage_class_name(e: &WebStorageError) -> &'static str { + match e { + WebStorageError::ContextNotSupported => "DOMExceptionNotSupportedError", + WebStorageError::Sqlite(_) => "Error", + WebStorageError::Io(e) => get_io_error_class(e), + WebStorageError::StorageExceeded => "DOMExceptionQuotaExceededError", + } +} + fn get_tls_error_class(e: &TlsError) -> &'static str { match e { TlsError::Rustls(_) => "Error", @@ -217,21 +690,1191 @@ fn get_broadcast_channel_error(error: &BroadcastChannelError) -> &'static str { } } +fn get_fetch_error(error: &FetchError) -> &'static str { + match error { + FetchError::Resource(e) => get_error_class_name(e).unwrap_or("Error"), + FetchError::Permission(e) => get_permission_check_error_class(e), + FetchError::NetworkError => "TypeError", + FetchError::FsNotGet(_) => "TypeError", + FetchError::InvalidUrl(_) => "TypeError", + FetchError::InvalidHeaderName(_) => "TypeError", + FetchError::InvalidHeaderValue(_) => "TypeError", + FetchError::DataUrl(_) => "TypeError", + FetchError::Base64(_) => "TypeError", + FetchError::BlobNotFound => "TypeError", + FetchError::SchemeNotSupported(_) => "TypeError", + FetchError::RequestCanceled => "TypeError", + FetchError::Http(_) => "Error", + FetchError::ClientCreate(e) => get_http_client_create_error(e), + FetchError::Url(e) => get_url_parse_error_class(e), + FetchError::Method(_) => "TypeError", + FetchError::ClientSend(_) => "TypeError", + FetchError::RequestBuilderHook(_) => "TypeError", + FetchError::Io(e) => get_io_error_class(e), + FetchError::Hyper(e) => get_hyper_error_class(e), + } +} + +fn get_http_client_create_error(error: &HttpClientCreateError) -> &'static str { + match error { + HttpClientCreateError::Tls(_) => "TypeError", + HttpClientCreateError::InvalidUserAgent(_) => "TypeError", + HttpClientCreateError::InvalidProxyUrl => "TypeError", + HttpClientCreateError::HttpVersionSelectionInvalid => "TypeError", + HttpClientCreateError::RootCertStore(_) => "TypeError", + } +} + +fn get_websocket_error(error: &WebsocketError) -> &'static str { + match error { + WebsocketError::Resource(e) => get_error_class_name(e).unwrap_or("Error"), + WebsocketError::Permission(e) => get_permission_check_error_class(e), + WebsocketError::Url(e) => get_url_parse_error_class(e), + WebsocketError::Io(e) => get_io_error_class(e), + WebsocketError::WebSocket(_) => "TypeError", + WebsocketError::ConnectionFailed(_) => "DOMExceptionNetworkError", + WebsocketError::Uri(_) => "Error", + WebsocketError::Canceled(e) => { + let io_err: io::Error = e.to_owned().into(); + get_io_error_class(&io_err) + } + } +} + +fn get_websocket_handshake_error(error: &HandshakeError) -> &'static str { + match error { + HandshakeError::RootStoreError(e) => { + get_error_class_name(e).unwrap_or("Error") + } + HandshakeError::Tls(e) => get_tls_error_class(e), + HandshakeError::MissingPath => "TypeError", + HandshakeError::Http(_) => "Error", + HandshakeError::InvalidHostname(_) => "TypeError", + HandshakeError::Io(e) => get_io_error_class(e), + HandshakeError::Rustls(_) => "Error", + HandshakeError::H2(_) => "Error", + HandshakeError::NoH2Alpn => "Error", + HandshakeError::InvalidStatusCode(_) => "Error", + HandshakeError::WebSocket(_) => "TypeError", + HandshakeError::HeaderName(_) => "TypeError", + HandshakeError::HeaderValue(_) => "TypeError", + } +} + +fn get_fs_ops_error(error: &FsOpsError) -> &'static str { + use FsOpsErrorKind::*; + match error.as_kind() { + Io(e) => get_io_error_class(e), + OperationError(e) => get_fs_error(&e.err), + Permission(e) => get_permission_check_error_class(e), + Resource(e) | Other(e) => get_error_class_name(e).unwrap_or("Error"), + InvalidUtf8(_) => "InvalidData", + StripPrefix(_) => "Error", + Canceled(e) => { + let io_err: io::Error = e.to_owned().into(); + get_io_error_class(&io_err) + } + InvalidSeekMode(_) => "TypeError", + InvalidControlCharacter(_) => "Error", + InvalidCharacter(_) => "Error", + #[cfg(windows)] + InvalidTrailingCharacter => "Error", + NotCapableAccess { .. } => "NotCapable", + NotCapable(_) => "NotCapable", + } +} + +fn get_kv_error(error: &KvError) -> &'static str { + use KvErrorKind::*; + match error.as_kind() { + DatabaseHandler(e) | Resource(e) | Kv(e) => { + get_error_class_name(e).unwrap_or("Error") + } + TooManyRanges(_) => "TypeError", + TooManyEntries(_) => "TypeError", + TooManyChecks(_) => "TypeError", + TooManyMutations(_) => "TypeError", + TooManyKeys(_) => "TypeError", + InvalidLimit => "TypeError", + InvalidBoundaryKey => "TypeError", + KeyTooLargeToRead(_) => "TypeError", + KeyTooLargeToWrite(_) => "TypeError", + TotalMutationTooLarge(_) => "TypeError", + TotalKeyTooLarge(_) => "TypeError", + Io(e) => get_io_error_class(e), + QueueMessageNotFound => "TypeError", + StartKeyNotInKeyspace => "TypeError", + EndKeyNotInKeyspace => "TypeError", + StartKeyGreaterThanEndKey => "TypeError", + InvalidCheck(e) => match e { + KvCheckError::InvalidVersionstamp => "TypeError", + KvCheckError::Io(e) => get_io_error_class(e), + }, + InvalidMutation(e) => match e { + KvMutationError::BigInt(_) => "Error", + KvMutationError::Io(e) => get_io_error_class(e), + KvMutationError::InvalidMutationWithValue(_) => "TypeError", + KvMutationError::InvalidMutationWithoutValue(_) => "TypeError", + }, + InvalidEnqueue(e) => get_io_error_class(e), + EmptyKey => "TypeError", + ValueTooLarge(_) => "TypeError", + EnqueuePayloadTooLarge(_) => "TypeError", + InvalidCursor => "TypeError", + CursorOutOfBounds => "TypeError", + InvalidRange => "TypeError", + } +} + +fn get_net_error(error: &NetError) -> &'static str { + match error { + NetError::ListenerClosed => "BadResource", + NetError::ListenerBusy => "Busy", + NetError::SocketClosed => "BadResource", + NetError::SocketClosedNotConnected => "NotConnected", + NetError::SocketBusy => "Busy", + NetError::Io(e) => get_io_error_class(e), + NetError::AcceptTaskOngoing => "Busy", + NetError::RootCertStore(e) | NetError::Resource(e) => { + get_error_class_name(e).unwrap_or("Error") + } + NetError::Permission(e) => get_permission_check_error_class(e), + NetError::NoResolvedAddress => "Error", + NetError::AddrParse(_) => "Error", + NetError::Map(e) => get_net_map_error(e), + NetError::Canceled(e) => { + let io_err: io::Error = e.to_owned().into(); + get_io_error_class(&io_err) + } + NetError::DnsNotFound(_) => "NotFound", + NetError::DnsNotConnected(_) => "NotConnected", + NetError::DnsTimedOut(_) => "TimedOut", + NetError::Dns(_) => "Error", + NetError::UnsupportedRecordType => "NotSupported", + NetError::InvalidUtf8(_) => "InvalidData", + NetError::UnexpectedKeyType => "Error", + NetError::InvalidHostname(_) => "TypeError", + NetError::TcpStreamBusy => "Busy", + NetError::Rustls(_) => "Error", + NetError::Tls(e) => get_tls_error_class(e), + NetError::ListenTlsRequiresKey => "InvalidData", + NetError::Reunite(_) => "Error", + } +} + +fn get_net_map_error(error: &deno_net::io::MapError) -> &'static str { + match error { + deno_net::io::MapError::Io(e) => get_io_error_class(e), + deno_net::io::MapError::NoResources => "Error", + } +} + +fn get_child_permission_error(e: &ChildPermissionError) -> &'static str { + match e { + ChildPermissionError::Escalation => "NotCapable", + ChildPermissionError::PathResolve(e) => get_path_resolve_error(e), + ChildPermissionError::NetDescriptorParse(_) => "URIError", + ChildPermissionError::EnvDescriptorParse(_) => "Error", + ChildPermissionError::SysDescriptorParse(e) => { + get_sys_descriptor_parse_error(e) + } + ChildPermissionError::RunDescriptorParse(e) => { + get_run_descriptor_parse_error(e) + } + } +} + +fn get_create_worker_error(error: &CreateWorkerError) -> &'static str { + match error { + CreateWorkerError::ClassicWorkers => "DOMExceptionNotSupportedError", + CreateWorkerError::Permission(e) => get_child_permission_error(e), + CreateWorkerError::ModuleResolution(e) => { + get_module_resolution_error_class(e) + } + CreateWorkerError::Io(e) => get_io_error_class(e), + CreateWorkerError::MessagePort(e) => get_web_message_port_error_class(e), + } +} + +fn get_tty_error(error: &TtyError) -> &'static str { + match error { + TtyError::Resource(e) | TtyError::Other(e) => { + get_error_class_name(e).unwrap_or("Error") + } + TtyError::Io(e) => get_io_error_class(e), + #[cfg(unix)] + TtyError::Nix(e) => get_nix_error_class(e), + } +} + +fn get_readline_error(error: &ReadlineError) -> &'static str { + match error { + ReadlineError::Io(e) => get_io_error_class(e), + ReadlineError::Eof => "Error", + ReadlineError::Interrupted => "Error", + #[cfg(unix)] + ReadlineError::Errno(e) => get_nix_error_class(e), + ReadlineError::WindowResized => "Error", + #[cfg(windows)] + ReadlineError::Decode(_) => "Error", + #[cfg(windows)] + ReadlineError::SystemError(_) => "Error", + _ => "Error", + } +} + +fn get_signal_error(error: &SignalError) -> &'static str { + match error { + SignalError::InvalidSignalStr(_) => "TypeError", + SignalError::InvalidSignalInt(_) => "TypeError", + SignalError::SignalNotAllowed(_) => "TypeError", + SignalError::Io(e) => get_io_error_class(e), + } +} + +fn get_fs_events_error(error: &FsEventsError) -> &'static str { + match error { + FsEventsError::Resource(e) => get_error_class_name(e).unwrap_or("Error"), + FsEventsError::Permission(e) => get_permission_check_error_class(e), + FsEventsError::Notify(e) => get_notify_error_class(e), + FsEventsError::Canceled(e) => { + let io_err: io::Error = e.to_owned().into(); + get_io_error_class(&io_err) + } + } +} + +fn get_http_start_error(error: &HttpStartError) -> &'static str { + match error { + HttpStartError::TcpStreamInUse => "Busy", + HttpStartError::TlsStreamInUse => "Busy", + HttpStartError::UnixSocketInUse => "Busy", + HttpStartError::ReuniteTcp(_) => "Error", + #[cfg(unix)] + HttpStartError::ReuniteUnix(_) => "Error", + HttpStartError::Io(e) => get_io_error_class(e), + HttpStartError::Other(e) => get_error_class_name(e).unwrap_or("Error"), + } +} + +fn get_process_error(error: &ProcessError) -> &'static str { + match error { + ProcessError::SpawnFailed { error, .. } => get_process_error(error), + ProcessError::FailedResolvingCwd(e) | ProcessError::Io(e) => { + get_io_error_class(e) + } + ProcessError::Permission(e) => get_permission_check_error_class(e), + ProcessError::Resource(e) => get_error_class_name(e).unwrap_or("Error"), + ProcessError::BorrowMut(_) => "Error", + ProcessError::Which(_) => "Error", + ProcessError::ChildProcessAlreadyTerminated => "TypeError", + ProcessError::Signal(e) => get_signal_error(e), + ProcessError::MissingCmd => "Error", + ProcessError::InvalidPid => "TypeError", + #[cfg(unix)] + ProcessError::Nix(e) => get_nix_error_class(e), + ProcessError::RunPermission(e) => match e { + CheckRunPermissionError::Permission(e) => { + get_permission_check_error_class(e) + } + CheckRunPermissionError::Other(e) => { + get_error_class_name(e).unwrap_or("Error") + } + }, + } +} + +fn get_http_error(error: &HttpError) -> &'static str { + match error { + HttpError::Canceled(e) => { + let io_err: io::Error = e.to_owned().into(); + get_io_error_class(&io_err) + } + HttpError::HyperV014(e) => get_hyper_v014_error_class(e), + HttpError::InvalidHeaderName(_) => "Error", + HttpError::InvalidHeaderValue(_) => "Error", + HttpError::Http(_) => "Error", + HttpError::ResponseHeadersAlreadySent => "Http", + HttpError::ConnectionClosedWhileSendingResponse => "Http", + HttpError::AlreadyInUse => "Http", + HttpError::Io(e) => get_io_error_class(e), + HttpError::NoResponseHeaders => "Http", + HttpError::ResponseAlreadyCompleted => "Http", + HttpError::UpgradeBodyUsed => "Http", + HttpError::Resource(e) | HttpError::Other(e) => { + get_error_class_name(e).unwrap_or("Error") + } + } +} + +fn get_http_next_error(error: &HttpNextError) -> &'static str { + match error { + HttpNextError::Io(e) => get_io_error_class(e), + HttpNextError::WebSocketUpgrade(e) => get_websocket_upgrade_error(e), + HttpNextError::Hyper(e) => get_hyper_error_class(e), + HttpNextError::JoinError(_) => "Error", + HttpNextError::Canceled(e) => { + let io_err: io::Error = e.to_owned().into(); + get_io_error_class(&io_err) + } + HttpNextError::UpgradeUnavailable(_) => "Error", + HttpNextError::HttpPropertyExtractor(e) | HttpNextError::Resource(e) => { + get_error_class_name(e).unwrap_or("Error") + } + } +} + +fn get_websocket_upgrade_error(error: &WebSocketUpgradeError) -> &'static str { + match error { + WebSocketUpgradeError::InvalidHeaders => "Http", + WebSocketUpgradeError::HttpParse(_) => "Error", + WebSocketUpgradeError::Http(_) => "Error", + WebSocketUpgradeError::Utf8(_) => "Error", + WebSocketUpgradeError::InvalidHeaderName(_) => "Error", + WebSocketUpgradeError::InvalidHeaderValue(_) => "Error", + WebSocketUpgradeError::InvalidHttpStatusLine => "Http", + WebSocketUpgradeError::UpgradeBufferAlreadyCompleted => "Http", + } +} + +fn get_fs_error(e: &FsError) -> &'static str { + match &e { + FsError::Io(e) => get_io_error_class(e), + FsError::FileBusy => "Busy", + FsError::NotSupported => "NotSupported", + FsError::NotCapable(_) => "NotCapable", + } +} + +mod node { + use super::get_error_class_name; + use super::get_io_error_class; + use super::get_permission_check_error_class; + use super::get_serde_json_error_class; + use super::get_url_parse_error_class; + pub use deno_node::ops::blocklist::BlocklistError; + pub use deno_node::ops::crypto::cipher::CipherContextError; + pub use deno_node::ops::crypto::cipher::CipherError; + pub use deno_node::ops::crypto::cipher::DecipherContextError; + pub use deno_node::ops::crypto::cipher::DecipherError; + pub use deno_node::ops::crypto::digest::HashError; + pub use deno_node::ops::crypto::keys::AsymmetricPrivateKeyDerError; + pub use deno_node::ops::crypto::keys::AsymmetricPrivateKeyError; + pub use deno_node::ops::crypto::keys::AsymmetricPublicKeyDerError; + pub use deno_node::ops::crypto::keys::AsymmetricPublicKeyError; + pub use deno_node::ops::crypto::keys::AsymmetricPublicKeyJwkError; + pub use deno_node::ops::crypto::keys::EcJwkError; + pub use deno_node::ops::crypto::keys::EdRawError; + pub use deno_node::ops::crypto::keys::ExportPrivateKeyPemError; + pub use deno_node::ops::crypto::keys::ExportPublicKeyPemError; + pub use deno_node::ops::crypto::keys::GenerateRsaPssError; + pub use deno_node::ops::crypto::keys::RsaJwkError; + pub use deno_node::ops::crypto::keys::RsaPssParamsParseError; + pub use deno_node::ops::crypto::keys::X509PublicKeyError; + pub use deno_node::ops::crypto::sign::KeyObjectHandlePrehashedSignAndVerifyError; + pub use deno_node::ops::crypto::x509::X509Error; + pub use deno_node::ops::crypto::DiffieHellmanError; + pub use deno_node::ops::crypto::EcdhEncodePubKey; + pub use deno_node::ops::crypto::HkdfError; + pub use deno_node::ops::crypto::Pbkdf2Error; + pub use deno_node::ops::crypto::PrivateEncryptDecryptError; + pub use deno_node::ops::crypto::ScryptAsyncError; + pub use deno_node::ops::crypto::SignEd25519Error; + pub use deno_node::ops::crypto::VerifyEd25519Error; + pub use deno_node::ops::fs::FsError; + pub use deno_node::ops::http2::Http2Error; + pub use deno_node::ops::idna::IdnaError; + pub use deno_node::ops::ipc::IpcError; + pub use deno_node::ops::ipc::IpcJsonStreamError; + use deno_node::ops::os::priority::PriorityError; + pub use deno_node::ops::os::OsError; + pub use deno_node::ops::require::RequireError; + use deno_node::ops::require::RequireErrorKind; + pub use deno_node::ops::worker_threads::WorkerThreadsFilenameError; + pub use deno_node::ops::zlib::brotli::BrotliError; + pub use deno_node::ops::zlib::mode::ModeError; + pub use deno_node::ops::zlib::ZlibError; + + pub fn get_blocklist_error(error: &BlocklistError) -> &'static str { + match error { + BlocklistError::AddrParse(_) => "Error", + BlocklistError::IpNetwork(_) => "Error", + BlocklistError::InvalidAddress => "Error", + BlocklistError::IpVersionMismatch => "Error", + } + } + + pub fn get_fs_error(error: &FsError) -> &'static str { + match error { + FsError::Permission(e) => get_permission_check_error_class(e), + FsError::Io(e) => get_io_error_class(e), + #[cfg(windows)] + FsError::PathHasNoRoot => "Error", + #[cfg(not(any(unix, windows)))] + FsError::UnsupportedPlatform => "Error", + FsError::Fs(e) => super::get_fs_error(e), + } + } + + pub fn get_idna_error(error: &IdnaError) -> &'static str { + match error { + IdnaError::InvalidInput => "RangeError", + IdnaError::InputTooLong => "Error", + IdnaError::IllegalInput => "RangeError", + } + } + + pub fn get_ipc_json_stream_error(error: &IpcJsonStreamError) -> &'static str { + match error { + IpcJsonStreamError::Io(e) => get_io_error_class(e), + IpcJsonStreamError::SimdJson(_) => "Error", + } + } + + pub fn get_ipc_error(error: &IpcError) -> &'static str { + match error { + IpcError::Resource(e) => get_error_class_name(e).unwrap_or("Error"), + IpcError::IpcJsonStream(e) => get_ipc_json_stream_error(e), + IpcError::Canceled(e) => { + let io_err: std::io::Error = e.to_owned().into(); + get_io_error_class(&io_err) + } + IpcError::SerdeJson(e) => get_serde_json_error_class(e), + } + } + + pub fn get_worker_threads_filename_error( + error: &WorkerThreadsFilenameError, + ) -> &'static str { + match error { + WorkerThreadsFilenameError::Permission(e) => { + get_error_class_name(e).unwrap_or("Error") + } + WorkerThreadsFilenameError::UrlParse(e) => get_url_parse_error_class(e), + WorkerThreadsFilenameError::InvalidRelativeUrl => "Error", + WorkerThreadsFilenameError::UrlFromPathString => "Error", + WorkerThreadsFilenameError::UrlToPathString => "Error", + WorkerThreadsFilenameError::UrlToPath => "Error", + WorkerThreadsFilenameError::FileNotFound(_) => "Error", + WorkerThreadsFilenameError::Fs(e) => super::get_fs_error(e), + } + } + + pub fn get_require_error(error: &RequireError) -> &'static str { + use RequireErrorKind::*; + match error.as_kind() { + UrlParse(e) => get_url_parse_error_class(e), + Permission(e) => get_error_class_name(e).unwrap_or("Error"), + PackageExportsResolve(_) + | PackageJsonLoad(_) + | ClosestPkgJson(_) + | FilePathConversion(_) + | UrlConversion(_) + | ReadModule(_) + | PackageImportsResolve(_) => "Error", + Fs(e) | UnableToGetCwd(e) => super::get_fs_error(e), + } + } + + pub fn get_http2_error(error: &Http2Error) -> &'static str { + match error { + Http2Error::Resource(e) => get_error_class_name(e).unwrap_or("Error"), + Http2Error::UrlParse(e) => get_url_parse_error_class(e), + Http2Error::H2(_) => "Error", + } + } + + pub fn get_os_error(error: &OsError) -> &'static str { + match error { + OsError::Priority(e) => match e { + PriorityError::Io(e) => get_io_error_class(e), + #[cfg(windows)] + PriorityError::InvalidPriority => "TypeError", + }, + OsError::Permission(e) => get_permission_check_error_class(e), + OsError::FailedToGetCpuInfo => "TypeError", + OsError::FailedToGetUserInfo(e) => get_io_error_class(e), + } + } + + pub fn get_brotli_error(error: &BrotliError) -> &'static str { + match error { + BrotliError::InvalidEncoderMode => "TypeError", + BrotliError::CompressFailed => "TypeError", + BrotliError::DecompressFailed => "TypeError", + BrotliError::Join(_) => "Error", + BrotliError::Resource(e) => get_error_class_name(e).unwrap_or("Error"), + BrotliError::Io(e) => get_io_error_class(e), + } + } + + pub fn get_mode_error(_: &ModeError) -> &'static str { + "Error" + } + + pub fn get_zlib_error(e: &ZlibError) -> &'static str { + match e { + ZlibError::NotInitialized => "TypeError", + ZlibError::Mode(e) => get_mode_error(e), + ZlibError::Other(e) => get_error_class_name(e).unwrap_or("Error"), + } + } + + pub fn get_crypto_cipher_context_error( + e: &CipherContextError, + ) -> &'static str { + match e { + CipherContextError::ContextInUse => "TypeError", + CipherContextError::Cipher(e) => get_crypto_cipher_error(e), + CipherContextError::Resource(e) => { + get_error_class_name(e).unwrap_or("Error") + } + } + } + + pub fn get_crypto_cipher_error(e: &CipherError) -> &'static str { + match e { + CipherError::InvalidIvLength => "TypeError", + CipherError::InvalidKeyLength => "RangeError", + CipherError::InvalidInitializationVector => "TypeError", + CipherError::CannotPadInputData => "TypeError", + CipherError::UnknownCipher(_) => "TypeError", + } + } + + pub fn get_crypto_decipher_context_error( + e: &DecipherContextError, + ) -> &'static str { + match e { + DecipherContextError::ContextInUse => "TypeError", + DecipherContextError::Decipher(e) => get_crypto_decipher_error(e), + DecipherContextError::Resource(e) => { + get_error_class_name(e).unwrap_or("Error") + } + } + } + + pub fn get_crypto_decipher_error(e: &DecipherError) -> &'static str { + match e { + DecipherError::InvalidIvLength => "TypeError", + DecipherError::InvalidKeyLength => "RangeError", + DecipherError::InvalidInitializationVector => "TypeError", + DecipherError::CannotUnpadInputData => "TypeError", + DecipherError::DataAuthenticationFailed => "TypeError", + DecipherError::SetAutoPaddingFalseAes128GcmUnsupported => "TypeError", + DecipherError::SetAutoPaddingFalseAes256GcmUnsupported => "TypeError", + DecipherError::UnknownCipher(_) => "TypeError", + } + } + + pub fn get_x509_error(_: &X509Error) -> &'static str { + "Error" + } + + pub fn get_crypto_key_object_handle_prehashed_sign_and_verify_error( + e: &KeyObjectHandlePrehashedSignAndVerifyError, + ) -> &'static str { + match e { + KeyObjectHandlePrehashedSignAndVerifyError::InvalidDsaSignatureEncoding => "TypeError", + KeyObjectHandlePrehashedSignAndVerifyError::KeyIsNotPrivate => "TypeError", + KeyObjectHandlePrehashedSignAndVerifyError::DigestNotAllowedForRsaSignature(_) => "TypeError", + KeyObjectHandlePrehashedSignAndVerifyError::FailedToSignDigestWithRsa => "Error", + KeyObjectHandlePrehashedSignAndVerifyError::DigestNotAllowedForRsaPssSignature(_) => "TypeError", + KeyObjectHandlePrehashedSignAndVerifyError::FailedToSignDigestWithRsaPss => "Error", + KeyObjectHandlePrehashedSignAndVerifyError::FailedToSignDigestWithDsa => "TypeError", + KeyObjectHandlePrehashedSignAndVerifyError::RsaPssHashAlgorithmUnsupported => "TypeError", + KeyObjectHandlePrehashedSignAndVerifyError::PrivateKeyDisallowsUsage { .. } => "TypeError", + KeyObjectHandlePrehashedSignAndVerifyError::FailedToSignDigest => "TypeError", + KeyObjectHandlePrehashedSignAndVerifyError::X25519KeyCannotBeUsedForSigning => "TypeError", + KeyObjectHandlePrehashedSignAndVerifyError::Ed25519KeyCannotBeUsedForPrehashedSigning => "TypeError", + KeyObjectHandlePrehashedSignAndVerifyError::DhKeyCannotBeUsedForSigning => "TypeError", + KeyObjectHandlePrehashedSignAndVerifyError::KeyIsNotPublicOrPrivate => "TypeError", + KeyObjectHandlePrehashedSignAndVerifyError::InvalidDsaSignature => "TypeError", + KeyObjectHandlePrehashedSignAndVerifyError::X25519KeyCannotBeUsedForVerification => "TypeError", + KeyObjectHandlePrehashedSignAndVerifyError::Ed25519KeyCannotBeUsedForPrehashedVerification => "TypeError", + KeyObjectHandlePrehashedSignAndVerifyError::DhKeyCannotBeUsedForVerification => "TypeError", + } + } + + pub fn get_crypto_hash_error(_: &HashError) -> &'static str { + "Error" + } + + pub fn get_asymmetric_public_key_jwk_error( + e: &AsymmetricPublicKeyJwkError, + ) -> &'static str { + match e { + AsymmetricPublicKeyJwkError::UnsupportedJwkEcCurveP224 => "TypeError", + AsymmetricPublicKeyJwkError::JwkExportNotImplementedForKeyType => { + "TypeError" + } + AsymmetricPublicKeyJwkError::KeyIsNotAsymmetricPublicKey => "TypeError", + } + } + + pub fn get_generate_rsa_pss_error(_: &GenerateRsaPssError) -> &'static str { + "TypeError" + } + + pub fn get_asymmetric_private_key_der_error( + e: &AsymmetricPrivateKeyDerError, + ) -> &'static str { + match e { + AsymmetricPrivateKeyDerError::KeyIsNotAsymmetricPrivateKey => "TypeError", + AsymmetricPrivateKeyDerError::InvalidRsaPrivateKey => "TypeError", + AsymmetricPrivateKeyDerError::ExportingNonRsaPrivateKeyAsPkcs1Unsupported => "TypeError", + AsymmetricPrivateKeyDerError::InvalidEcPrivateKey => "TypeError", + AsymmetricPrivateKeyDerError::ExportingNonEcPrivateKeyAsSec1Unsupported => "TypeError", + AsymmetricPrivateKeyDerError::ExportingNonRsaPssPrivateKeyAsPkcs8Unsupported => "Error", + AsymmetricPrivateKeyDerError::InvalidDsaPrivateKey => "TypeError", + AsymmetricPrivateKeyDerError::InvalidX25519PrivateKey => "TypeError", + AsymmetricPrivateKeyDerError::InvalidEd25519PrivateKey => "TypeError", + AsymmetricPrivateKeyDerError::InvalidDhPrivateKey => "TypeError", + AsymmetricPrivateKeyDerError::UnsupportedKeyType(_) => "TypeError", + } + } + + pub fn get_asymmetric_public_key_der_error( + _: &AsymmetricPublicKeyDerError, + ) -> &'static str { + "TypeError" + } + + pub fn get_export_public_key_pem_error( + e: &ExportPublicKeyPemError, + ) -> &'static str { + match e { + ExportPublicKeyPemError::AsymmetricPublicKeyDer(e) => { + get_asymmetric_public_key_der_error(e) + } + ExportPublicKeyPemError::VeryLargeData => "TypeError", + ExportPublicKeyPemError::Der(_) => "Error", + } + } + + pub fn get_export_private_key_pem_error( + e: &ExportPrivateKeyPemError, + ) -> &'static str { + match e { + ExportPrivateKeyPemError::AsymmetricPublicKeyDer(e) => { + get_asymmetric_private_key_der_error(e) + } + ExportPrivateKeyPemError::VeryLargeData => "TypeError", + ExportPrivateKeyPemError::Der(_) => "Error", + } + } + + pub fn get_x509_public_key_error(e: &X509PublicKeyError) -> &'static str { + match e { + X509PublicKeyError::X509(_) => "Error", + X509PublicKeyError::Rsa(_) => "Error", + X509PublicKeyError::Asn1(_) => "Error", + X509PublicKeyError::Ec(_) => "Error", + X509PublicKeyError::UnsupportedEcNamedCurve => "TypeError", + X509PublicKeyError::MissingEcParameters => "TypeError", + X509PublicKeyError::MalformedDssPublicKey => "TypeError", + X509PublicKeyError::UnsupportedX509KeyType => "TypeError", + } + } + + pub fn get_rsa_jwk_error(e: &RsaJwkError) -> &'static str { + match e { + RsaJwkError::Base64(_) => "Error", + RsaJwkError::Rsa(_) => "Error", + RsaJwkError::MissingRsaPrivateComponent => "TypeError", + } + } + + pub fn get_ec_jwk_error(e: &EcJwkError) -> &'static str { + match e { + EcJwkError::Ec(_) => "Error", + EcJwkError::UnsupportedCurve(_) => "TypeError", + } + } + + pub fn get_ed_raw_error(e: &EdRawError) -> &'static str { + match e { + EdRawError::Ed25519Signature(_) => "Error", + EdRawError::InvalidEd25519Key => "TypeError", + EdRawError::UnsupportedCurve => "TypeError", + } + } + + pub fn get_pbkdf2_error(e: &Pbkdf2Error) -> &'static str { + match e { + Pbkdf2Error::UnsupportedDigest(_) => "TypeError", + Pbkdf2Error::Join(_) => "Error", + } + } + + pub fn get_scrypt_async_error(e: &ScryptAsyncError) -> &'static str { + match e { + ScryptAsyncError::Join(_) => "Error", + ScryptAsyncError::Other(e) => get_error_class_name(e).unwrap_or("Error"), + } + } + + pub fn get_hkdf_error_error(e: &HkdfError) -> &'static str { + match e { + HkdfError::ExpectedSecretKey => "TypeError", + HkdfError::HkdfExpandFailed => "TypeError", + HkdfError::UnsupportedDigest(_) => "TypeError", + HkdfError::Join(_) => "Error", + } + } + + pub fn get_rsa_pss_params_parse_error( + _: &RsaPssParamsParseError, + ) -> &'static str { + "TypeError" + } + + pub fn get_asymmetric_private_key_error( + e: &AsymmetricPrivateKeyError, + ) -> &'static str { + match e { + AsymmetricPrivateKeyError::InvalidPemPrivateKeyInvalidUtf8(_) => "TypeError", + AsymmetricPrivateKeyError::InvalidEncryptedPemPrivateKey => "TypeError", + AsymmetricPrivateKeyError::InvalidPemPrivateKey => "TypeError", + AsymmetricPrivateKeyError::EncryptedPrivateKeyRequiresPassphraseToDecrypt => "TypeError", + AsymmetricPrivateKeyError::InvalidPkcs1PrivateKey => "TypeError", + AsymmetricPrivateKeyError::InvalidSec1PrivateKey => "TypeError", + AsymmetricPrivateKeyError::UnsupportedPemLabel(_) => "TypeError", + AsymmetricPrivateKeyError::RsaPssParamsParse(e) => get_rsa_pss_params_parse_error(e), + AsymmetricPrivateKeyError::InvalidEncryptedPkcs8PrivateKey => "TypeError", + AsymmetricPrivateKeyError::InvalidPkcs8PrivateKey => "TypeError", + AsymmetricPrivateKeyError::Pkcs1PrivateKeyDoesNotSupportEncryptionWithPassphrase => "TypeError", + AsymmetricPrivateKeyError::Sec1PrivateKeyDoesNotSupportEncryptionWithPassphrase => "TypeError", + AsymmetricPrivateKeyError::UnsupportedEcNamedCurve => "TypeError", + AsymmetricPrivateKeyError::InvalidPrivateKey => "TypeError", + AsymmetricPrivateKeyError::InvalidDsaPrivateKey => "TypeError", + AsymmetricPrivateKeyError::MalformedOrMissingNamedCurveInEcParameters => "TypeError", + AsymmetricPrivateKeyError::UnsupportedKeyType(_) => "TypeError", + AsymmetricPrivateKeyError::UnsupportedKeyFormat(_) => "TypeError", + AsymmetricPrivateKeyError::InvalidX25519PrivateKey => "TypeError", + AsymmetricPrivateKeyError::X25519PrivateKeyIsWrongLength => "TypeError", + AsymmetricPrivateKeyError::InvalidEd25519PrivateKey => "TypeError", + AsymmetricPrivateKeyError::MissingDhParameters => "TypeError", + AsymmetricPrivateKeyError::UnsupportedPrivateKeyOid => "TypeError", + } + } + + pub fn get_asymmetric_public_key_error( + e: &AsymmetricPublicKeyError, + ) -> &'static str { + match e { + AsymmetricPublicKeyError::InvalidPemPrivateKeyInvalidUtf8(_) => { + "TypeError" + } + AsymmetricPublicKeyError::InvalidPemPublicKey => "TypeError", + AsymmetricPublicKeyError::InvalidPkcs1PublicKey => "TypeError", + AsymmetricPublicKeyError::AsymmetricPrivateKey(e) => { + get_asymmetric_private_key_error(e) + } + AsymmetricPublicKeyError::InvalidX509Certificate => "TypeError", + AsymmetricPublicKeyError::X509(_) => "Error", + AsymmetricPublicKeyError::X509PublicKey(e) => { + get_x509_public_key_error(e) + } + AsymmetricPublicKeyError::UnsupportedPemLabel(_) => "TypeError", + AsymmetricPublicKeyError::InvalidSpkiPublicKey => "TypeError", + AsymmetricPublicKeyError::UnsupportedKeyType(_) => "TypeError", + AsymmetricPublicKeyError::UnsupportedKeyFormat(_) => "TypeError", + AsymmetricPublicKeyError::Spki(_) => "Error", + AsymmetricPublicKeyError::Pkcs1(_) => "Error", + AsymmetricPublicKeyError::RsaPssParamsParse(_) => "TypeError", + AsymmetricPublicKeyError::MalformedDssPublicKey => "TypeError", + AsymmetricPublicKeyError::MalformedOrMissingNamedCurveInEcParameters => { + "TypeError" + } + AsymmetricPublicKeyError::MalformedOrMissingPublicKeyInEcSpki => { + "TypeError" + } + AsymmetricPublicKeyError::Ec(_) => "Error", + AsymmetricPublicKeyError::UnsupportedEcNamedCurve => "TypeError", + AsymmetricPublicKeyError::MalformedOrMissingPublicKeyInX25519Spki => { + "TypeError" + } + AsymmetricPublicKeyError::X25519PublicKeyIsTooShort => "TypeError", + AsymmetricPublicKeyError::InvalidEd25519PublicKey => "TypeError", + AsymmetricPublicKeyError::MissingDhParameters => "TypeError", + AsymmetricPublicKeyError::MalformedDhParameters => "TypeError", + AsymmetricPublicKeyError::MalformedOrMissingPublicKeyInDhSpki => { + "TypeError" + } + AsymmetricPublicKeyError::UnsupportedPrivateKeyOid => "TypeError", + } + } + + pub fn get_private_encrypt_decrypt_error( + e: &PrivateEncryptDecryptError, + ) -> &'static str { + match e { + PrivateEncryptDecryptError::Pkcs8(_) => "Error", + PrivateEncryptDecryptError::Spki(_) => "Error", + PrivateEncryptDecryptError::Utf8(_) => "Error", + PrivateEncryptDecryptError::Rsa(_) => "Error", + PrivateEncryptDecryptError::UnknownPadding => "TypeError", + } + } + + pub fn get_ecdh_encode_pub_key_error(e: &EcdhEncodePubKey) -> &'static str { + match e { + EcdhEncodePubKey::InvalidPublicKey => "TypeError", + EcdhEncodePubKey::UnsupportedCurve => "TypeError", + EcdhEncodePubKey::Sec1(_) => "Error", + } + } + + pub fn get_diffie_hellman_error(_: &DiffieHellmanError) -> &'static str { + "TypeError" + } + + pub fn get_sign_ed25519_error(_: &SignEd25519Error) -> &'static str { + "TypeError" + } + + pub fn get_verify_ed25519_error(_: &VerifyEd25519Error) -> &'static str { + "TypeError" + } +} + +fn get_os_error(error: &OsError) -> &'static str { + match error { + OsError::Permission(e) => get_permission_check_error_class(e), + OsError::InvalidUtf8(_) => "InvalidData", + OsError::EnvEmptyKey => "TypeError", + OsError::EnvInvalidKey(_) => "TypeError", + OsError::EnvInvalidValue(_) => "TypeError", + OsError::Io(e) => get_io_error_class(e), + OsError::Var(e) => get_env_var_error_class(e), + } +} + +fn get_sync_fetch_error(error: &SyncFetchError) -> &'static str { + match error { + SyncFetchError::BlobUrlsNotSupportedInContext => "TypeError", + SyncFetchError::Io(e) => get_io_error_class(e), + SyncFetchError::InvalidScriptUrl => "TypeError", + SyncFetchError::InvalidStatusCode(_) => "TypeError", + SyncFetchError::ClassicScriptSchemeUnsupportedInWorkers(_) => "TypeError", + SyncFetchError::InvalidUri(_) => "Error", + SyncFetchError::InvalidMimeType(_) => "DOMExceptionNetworkError", + SyncFetchError::MissingMimeType => "DOMExceptionNetworkError", + SyncFetchError::Fetch(e) => get_fetch_error(e), + SyncFetchError::Join(_) => "Error", + SyncFetchError::Other(e) => get_error_class_name(e).unwrap_or("Error"), + } +} + pub fn get_error_class_name(e: &AnyError) -> Option<&'static str> { deno_core::error::get_custom_error_class(e) - .or_else(|| deno_webgpu::error::get_error_class_name(e)) - .or_else(|| deno_web::get_error_class_name(e)) - .or_else(|| deno_webstorage::get_not_supported_error_class_name(e)) - .or_else(|| deno_websocket::get_network_error_class_name(e)) + .or_else(|| { + e.downcast_ref::<ChildPermissionError>() + .map(get_child_permission_error) + }) + .or_else(|| { + e.downcast_ref::<PermissionCheckError>() + .map(get_permission_check_error_class) + }) + .or_else(|| { + e.downcast_ref::<PermissionError>() + .map(get_permission_error_class) + }) + .or_else(|| e.downcast_ref::<FsError>().map(get_fs_error)) + .or_else(|| { + e.downcast_ref::<node::BlocklistError>() + .map(node::get_blocklist_error) + }) + .or_else(|| e.downcast_ref::<node::FsError>().map(node::get_fs_error)) + .or_else(|| { + e.downcast_ref::<node::IdnaError>() + .map(node::get_idna_error) + }) + .or_else(|| { + e.downcast_ref::<node::IpcJsonStreamError>() + .map(node::get_ipc_json_stream_error) + }) + .or_else(|| e.downcast_ref::<node::IpcError>().map(node::get_ipc_error)) + .or_else(|| { + e.downcast_ref::<node::WorkerThreadsFilenameError>() + .map(node::get_worker_threads_filename_error) + }) + .or_else(|| { + e.downcast_ref::<node::RequireError>() + .map(node::get_require_error) + }) + .or_else(|| { + e.downcast_ref::<node::Http2Error>() + .map(node::get_http2_error) + }) + .or_else(|| e.downcast_ref::<node::OsError>().map(node::get_os_error)) + .or_else(|| { + e.downcast_ref::<node::BrotliError>() + .map(node::get_brotli_error) + }) + .or_else(|| { + e.downcast_ref::<node::ModeError>() + .map(node::get_mode_error) + }) + .or_else(|| { + e.downcast_ref::<node::ZlibError>() + .map(node::get_zlib_error) + }) + .or_else(|| { + e.downcast_ref::<node::CipherError>() + .map(node::get_crypto_cipher_error) + }) + .or_else(|| { + e.downcast_ref::<node::CipherContextError>() + .map(node::get_crypto_cipher_context_error) + }) + .or_else(|| { + e.downcast_ref::<node::DecipherError>() + .map(node::get_crypto_decipher_error) + }) + .or_else(|| { + e.downcast_ref::<node::DecipherContextError>() + .map(node::get_crypto_decipher_context_error) + }) + .or_else(|| { + e.downcast_ref::<node::X509Error>() + .map(node::get_x509_error) + }) + .or_else(|| { + e.downcast_ref::<node::KeyObjectHandlePrehashedSignAndVerifyError>() + .map(node::get_crypto_key_object_handle_prehashed_sign_and_verify_error) + }) + .or_else(|| { + e.downcast_ref::<node::HashError>() + .map(node::get_crypto_hash_error) + }) + .or_else(|| { + e.downcast_ref::<node::AsymmetricPublicKeyJwkError>() + .map(node::get_asymmetric_public_key_jwk_error) + }) + .or_else(|| { + e.downcast_ref::<node::GenerateRsaPssError>() + .map(node::get_generate_rsa_pss_error) + }) + .or_else(|| { + e.downcast_ref::<node::AsymmetricPrivateKeyDerError>() + .map(node::get_asymmetric_private_key_der_error) + }) + .or_else(|| { + e.downcast_ref::<node::AsymmetricPublicKeyDerError>() + .map(node::get_asymmetric_public_key_der_error) + }) + .or_else(|| { + e.downcast_ref::<node::ExportPublicKeyPemError>() + .map(node::get_export_public_key_pem_error) + }) + .or_else(|| { + e.downcast_ref::<node::ExportPrivateKeyPemError>() + .map(node::get_export_private_key_pem_error) + }) + .or_else(|| { + e.downcast_ref::<node::RsaJwkError>() + .map(node::get_rsa_jwk_error) + }) + .or_else(|| { + e.downcast_ref::<node::EcJwkError>() + .map(node::get_ec_jwk_error) + }) + .or_else(|| { + e.downcast_ref::<node::EdRawError>() + .map(node::get_ed_raw_error) + }) + .or_else(|| { + e.downcast_ref::<node::Pbkdf2Error>() + .map(node::get_pbkdf2_error) + }) + .or_else(|| { + e.downcast_ref::<node::ScryptAsyncError>() + .map(node::get_scrypt_async_error) + }) + .or_else(|| { + e.downcast_ref::<node::HkdfError>() + .map(node::get_hkdf_error_error) + }) + .or_else(|| { + e.downcast_ref::<node::RsaPssParamsParseError>() + .map(node::get_rsa_pss_params_parse_error) + }) + .or_else(|| { + e.downcast_ref::<node::AsymmetricPrivateKeyError>() + .map(node::get_asymmetric_private_key_error) + }) + .or_else(|| { + e.downcast_ref::<node::AsymmetricPublicKeyError>() + .map(node::get_asymmetric_public_key_error) + }) + .or_else(|| { + e.downcast_ref::<node::PrivateEncryptDecryptError>() + .map(node::get_private_encrypt_decrypt_error) + }) + .or_else(|| { + e.downcast_ref::<node::EcdhEncodePubKey>() + .map(node::get_ecdh_encode_pub_key_error) + }) + .or_else(|| { + e.downcast_ref::<node::DiffieHellmanError>() + .map(node::get_diffie_hellman_error) + }) + .or_else(|| { + e.downcast_ref::<node::SignEd25519Error>() + .map(node::get_sign_ed25519_error) + }) + .or_else(|| { + e.downcast_ref::<node::VerifyEd25519Error>() + .map(node::get_verify_ed25519_error) + }) + .or_else(|| e.downcast_ref::<NApiError>().map(get_napi_error_class)) + .or_else(|| e.downcast_ref::<WebError>().map(get_web_error_class)) + .or_else(|| { + e.downcast_ref::<CreateWorkerError>() + .map(get_create_worker_error) + }) + .or_else(|| e.downcast_ref::<TtyError>().map(get_tty_error)) + .or_else(|| e.downcast_ref::<ReadlineError>().map(get_readline_error)) + .or_else(|| e.downcast_ref::<SignalError>().map(get_signal_error)) + .or_else(|| e.downcast_ref::<FsEventsError>().map(get_fs_events_error)) + .or_else(|| e.downcast_ref::<HttpStartError>().map(get_http_start_error)) + .or_else(|| e.downcast_ref::<ProcessError>().map(get_process_error)) + .or_else(|| e.downcast_ref::<OsError>().map(get_os_error)) + .or_else(|| e.downcast_ref::<SyncFetchError>().map(get_sync_fetch_error)) + .or_else(|| { + e.downcast_ref::<CompressionError>() + .map(get_web_compression_error_class) + }) + .or_else(|| { + e.downcast_ref::<MessagePortError>() + .map(get_web_message_port_error_class) + }) + .or_else(|| { + e.downcast_ref::<StreamResourceError>() + .map(get_web_stream_resource_error_class) + }) + .or_else(|| e.downcast_ref::<BlobError>().map(get_web_blob_error_class)) + .or_else(|| e.downcast_ref::<IRError>().map(|_| "TypeError")) + .or_else(|| e.downcast_ref::<ReprError>().map(get_ffi_repr_error_class)) + .or_else(|| e.downcast_ref::<HttpError>().map(get_http_error)) + .or_else(|| e.downcast_ref::<HttpNextError>().map(get_http_next_error)) + .or_else(|| { + e.downcast_ref::<WebSocketUpgradeError>() + .map(get_websocket_upgrade_error) + }) + .or_else(|| e.downcast_ref::<FsOpsError>().map(get_fs_ops_error)) + .or_else(|| { + e.downcast_ref::<DlfcnError>() + .map(get_ffi_dlfcn_error_class) + }) + .or_else(|| { + e.downcast_ref::<StaticError>() + .map(get_ffi_static_error_class) + }) + .or_else(|| { + e.downcast_ref::<CallbackError>() + .map(get_ffi_callback_error_class) + }) + .or_else(|| e.downcast_ref::<CallError>().map(get_ffi_call_error_class)) .or_else(|| e.downcast_ref::<TlsError>().map(get_tls_error_class)) .or_else(|| e.downcast_ref::<CronError>().map(get_cron_error_class)) .or_else(|| e.downcast_ref::<CanvasError>().map(get_canvas_error)) .or_else(|| e.downcast_ref::<CacheError>().map(get_cache_error)) + .or_else(|| e.downcast_ref::<WebsocketError>().map(get_websocket_error)) + .or_else(|| { + e.downcast_ref::<HandshakeError>() + .map(get_websocket_handshake_error) + }) + .or_else(|| e.downcast_ref::<KvError>().map(get_kv_error)) + .or_else(|| e.downcast_ref::<FetchError>().map(get_fetch_error)) + .or_else(|| { + e.downcast_ref::<HttpClientCreateError>() + .map(get_http_client_create_error) + }) + .or_else(|| e.downcast_ref::<NetError>().map(get_net_error)) + .or_else(|| { + e.downcast_ref::<deno_net::io::MapError>() + .map(get_net_map_error) + }) .or_else(|| { e.downcast_ref::<BroadcastChannelError>() .map(get_broadcast_channel_error) }) .or_else(|| { + e.downcast_ref::<deno_webgpu::InitError>() + .map(get_webgpu_error_class) + }) + .or_else(|| { + e.downcast_ref::<deno_webgpu::buffer::BufferError>() + .map(get_webgpu_buffer_error_class) + }) + .or_else(|| { + e.downcast_ref::<deno_webgpu::bundle::BundleError>() + .map(get_webgpu_bundle_error_class) + }) + .or_else(|| { + e.downcast_ref::<deno_webgpu::byow::ByowError>() + .map(get_webgpu_byow_error_class) + }) + .or_else(|| { + e.downcast_ref::<deno_webgpu::render_pass::RenderPassError>() + .map(get_webgpu_render_pass_error_class) + }) + .or_else(|| { + e.downcast_ref::<deno_webgpu::surface::SurfaceError>() + .map(get_webgpu_surface_error_class) + }) + .or_else(|| { + e.downcast_ref::<DecryptError>() + .map(get_crypto_decrypt_error_class) + }) + .or_else(|| { + e.downcast_ref::<EncryptError>() + .map(get_crypto_encrypt_error_class) + }) + .or_else(|| { + e.downcast_ref::<deno_crypto::SharedError>() + .map(get_crypto_shared_error_class) + }) + .or_else(|| { + e.downcast_ref::<deno_crypto::Ed25519Error>() + .map(get_crypto_ed25519_error_class) + }) + .or_else(|| { + e.downcast_ref::<ExportKeyError>() + .map(get_crypto_export_key_error_class) + }) + .or_else(|| { + e.downcast_ref::<GenerateKeyError>() + .map(get_crypto_generate_key_error_class) + }) + .or_else(|| { + e.downcast_ref::<ImportKeyError>() + .map(get_crypto_import_key_error_class) + }) + .or_else(|| { + e.downcast_ref::<deno_crypto::X448Error>() + .map(get_crypto_x448_error_class) + }) + .or_else(|| { + e.downcast_ref::<deno_crypto::X25519Error>() + .map(get_crypto_x25519_error_class) + }) + .or_else(|| { + e.downcast_ref::<deno_crypto::Error>() + .map(get_crypto_error_class) + }) + .or_else(|| { + e.downcast_ref::<WebStorageError>() + .map(get_webstorage_class_name) + }) + .or_else(|| { + e.downcast_ref::<deno_url::UrlPatternError>() + .map(|_| "TypeError") + }) + .or_else(|| { e.downcast_ref::<dlopen2::Error>() .map(get_dlopen_error_class) }) diff --git a/runtime/examples/extension/main.rs b/runtime/examples/extension/main.rs index 9889b28dc..1ff16ec83 100644 --- a/runtime/examples/extension/main.rs +++ b/runtime/examples/extension/main.rs @@ -50,6 +50,7 @@ async fn main() -> Result<(), AnyError> { node_services: Default::default(), npm_process_state_provider: Default::default(), root_cert_store_provider: Default::default(), + fetch_dns_resolver: Default::default(), shared_array_buffer_store: Default::default(), compiled_wasm_module_store: Default::default(), v8_code_cache: Default::default(), diff --git a/runtime/fmt_errors.rs b/runtime/fmt_errors.rs index 44a947732..6c05fbc63 100644 --- a/runtime/fmt_errors.rs +++ b/runtime/fmt_errors.rs @@ -1,11 +1,10 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. //! This mod provides DenoError to unify errors across Deno. +use color_print::cformat; +use color_print::cstr; use deno_core::error::format_frame; use deno_core::error::JsError; -use deno_terminal::colors::cyan; -use deno_terminal::colors::italic_bold; -use deno_terminal::colors::red; -use deno_terminal::colors::yellow; +use deno_terminal::colors; use std::fmt::Write as _; #[derive(Debug, Clone)] @@ -24,6 +23,7 @@ struct IndexedErrorReference<'a> { enum FixSuggestionKind { Info, Hint, + Docs, } #[derive(Debug)] @@ -66,6 +66,13 @@ impl<'a> FixSuggestion<'a> { message: FixSuggestionMessage::Multiline(messages), } } + + pub fn docs(url: &'a str) -> Self { + Self { + kind: FixSuggestionKind::Docs, + message: FixSuggestionMessage::Single(url), + } + } } struct AnsiColors; @@ -78,10 +85,10 @@ impl deno_core::error::ErrorFormat for AnsiColors { use deno_core::error::ErrorElement::*; match element { Anonymous | NativeFrame | FileName | EvalOrigin => { - cyan(s).to_string().into() + colors::cyan(s).to_string().into() } - LineNumber | ColumnNumber => yellow(s).to_string().into(), - FunctionName | PromiseAll => italic_bold(s).to_string().into(), + LineNumber | ColumnNumber => colors::yellow(s).to_string().into(), + FunctionName | PromiseAll => colors::italic_bold(s).to_string().into(), } } } @@ -114,7 +121,7 @@ fn format_maybe_source_line( if column_number as usize > source_line.len() { return format!( "\n{} Couldn't format source line: Column {} is out of bounds (source may have changed at runtime)", - yellow("Warning"), column_number, + colors::yellow("Warning"), column_number, ); } @@ -127,9 +134,9 @@ fn format_maybe_source_line( } s.push('^'); let color_underline = if is_error { - red(&s).to_string() + colors::red(&s).to_string() } else { - cyan(&s).to_string() + colors::cyan(&s).to_string() }; let indent = format!("{:indent$}", "", indent = level); @@ -200,7 +207,8 @@ fn format_js_error_inner( if let Some(circular) = &circular { if js_error.is_same_error(circular.reference.to) { - write!(s, " {}", cyan(format!("<ref *{}>", circular.index))).unwrap(); + write!(s, " {}", colors::cyan(format!("<ref *{}>", circular.index))) + .unwrap(); } } @@ -238,7 +246,8 @@ fn format_js_error_inner( .unwrap_or(false); let error_string = if is_caused_by_circular { - cyan(format!("[Circular *{}]", circular.unwrap().index)).to_string() + colors::cyan(format!("[Circular *{}]", circular.unwrap().index)) + .to_string() } else { format_js_error_inner(cause, circular, false, vec![]) }; @@ -255,12 +264,23 @@ fn format_js_error_inner( for (index, suggestion) in suggestions.iter().enumerate() { write!(s, " ").unwrap(); match suggestion.kind { - FixSuggestionKind::Hint => write!(s, "{} ", cyan("hint:")).unwrap(), - FixSuggestionKind::Info => write!(s, "{} ", yellow("info:")).unwrap(), + FixSuggestionKind::Hint => { + write!(s, "{} ", colors::cyan("hint:")).unwrap() + } + FixSuggestionKind::Info => { + write!(s, "{} ", colors::yellow("info:")).unwrap() + } + FixSuggestionKind::Docs => { + write!(s, "{} ", colors::green("docs:")).unwrap() + } }; match suggestion.message { FixSuggestionMessage::Single(msg) => { - write!(s, "{}", msg).unwrap(); + if matches!(suggestion.kind, FixSuggestionKind::Docs) { + write!(s, "{}", cformat!("<u>{}</>", msg)).unwrap(); + } else { + write!(s, "{}", msg).unwrap(); + } } FixSuggestionMessage::Multiline(messages) => { for (idx, message) in messages.iter().enumerate() { @@ -282,28 +302,174 @@ fn format_js_error_inner( s } -/// Format a [`JsError`] for terminal output. -pub fn format_js_error(js_error: &JsError) -> String { - let circular = - find_recursive_cause(js_error).map(|reference| IndexedErrorReference { - reference, - index: 1, - }); +fn get_suggestions_for_terminal_errors(e: &JsError) -> Vec<FixSuggestion> { + if let Some(msg) = &e.message { + if msg.contains("module is not defined") + || msg.contains("exports is not defined") + || msg.contains("require is not defined") + { + return vec![ + FixSuggestion::info_multiline(&[ + cstr!("Deno supports CommonJS modules in <u>.cjs</> files, or when the closest"), + cstr!("<u>package.json</> has a <i>\"type\": \"commonjs\"</> option.") + ]), + FixSuggestion::hint_multiline(&[ + "Rewrite this module to ESM,", + cstr!("or change the file extension to <u>.cjs</u>,"), + cstr!("or add <u>package.json</> next to the file with <i>\"type\": \"commonjs\"</> option."), + ]), + FixSuggestion::docs("https://docs.deno.com/go/commonjs"), + ]; + } else if msg.contains("__filename is not defined") { + return vec![ + FixSuggestion::info(cstr!( + "<u>__filename</> global is not available in ES modules." + )), + FixSuggestion::hint(cstr!("Use <u>import.meta.filename</> instead.")), + ]; + } else if msg.contains("__dirname is not defined") { + return vec![ + FixSuggestion::info(cstr!( + "<u>__dirname</> global is not available in ES modules." + )), + FixSuggestion::hint(cstr!("Use <u>import.meta.dirname</> instead.")), + ]; + } else if msg.contains("Buffer is not defined") { + return vec![ + FixSuggestion::info(cstr!( + "<u>Buffer</> is not available in the global scope in Deno." + )), + FixSuggestion::hint_multiline(&[ + cstr!("Import it explicitly with <u>import { Buffer } from \"node:buffer\";</>,"), + cstr!("or run again with <u>--unstable-node-globals</> flag to add this global."), + ]), + ]; + } else if msg.contains("clearImmediate is not defined") { + return vec![ + FixSuggestion::info(cstr!( + "<u>clearImmediate</> is not available in the global scope in Deno." + )), + FixSuggestion::hint_multiline(&[ + cstr!("Import it explicitly with <u>import { clearImmediate } from \"node:timers\";</>,"), + cstr!("or run again with <u>--unstable-node-globals</> flag to add this global."), + ]), + ]; + } else if msg.contains("setImmediate is not defined") { + return vec![ + FixSuggestion::info(cstr!( + "<u>setImmediate</> is not available in the global scope in Deno." + )), + FixSuggestion::hint_multiline( + &[cstr!("Import it explicitly with <u>import { setImmediate } from \"node:timers\";</>,"), + cstr!("or run again with <u>--unstable-node-globals</> flag to add this global."), + ]), + ]; + } else if msg.contains("global is not defined") { + return vec![ + FixSuggestion::info(cstr!( + "<u>global</> is not available in the global scope in Deno." + )), + FixSuggestion::hint_multiline(&[ + cstr!("Use <u>globalThis</> instead, or assign <u>globalThis.global = globalThis</>,"), + cstr!("or run again with <u>--unstable-node-globals</> flag to add this global."), + ]), + ]; + } else if msg.contains("openKv is not a function") { + return vec![ + FixSuggestion::info("Deno.openKv() is an unstable API."), + FixSuggestion::hint( + "Run again with `--unstable-kv` flag to enable this API.", + ), + ]; + } else if msg.contains("cron is not a function") { + return vec![ + FixSuggestion::info("Deno.cron() is an unstable API."), + FixSuggestion::hint( + "Run again with `--unstable-cron` flag to enable this API.", + ), + ]; + } else if msg.contains("WebSocketStream is not defined") { + return vec![ + FixSuggestion::info("new WebSocketStream() is an unstable API."), + FixSuggestion::hint( + "Run again with `--unstable-net` flag to enable this API.", + ), + ]; + } else if msg.contains("Temporal is not defined") { + return vec![ + FixSuggestion::info("Temporal is an unstable API."), + FixSuggestion::hint( + "Run again with `--unstable-temporal` flag to enable this API.", + ), + ]; + } else if msg.contains("BroadcastChannel is not defined") { + return vec![ + FixSuggestion::info("BroadcastChannel is an unstable API."), + FixSuggestion::hint( + "Run again with `--unstable-broadcast-channel` flag to enable this API.", + ), + ]; + } else if msg.contains("window is not defined") { + return vec![ + FixSuggestion::info("window global is not available in Deno 2."), + FixSuggestion::hint("Replace `window` with `globalThis`."), + ]; + } else if msg.contains("UnsafeWindowSurface is not a constructor") { + return vec![ + FixSuggestion::info("Deno.UnsafeWindowSurface is an unstable API."), + FixSuggestion::hint( + "Run again with `--unstable-webgpu` flag to enable this API.", + ), + ]; + // Try to capture errors like: + // ``` + // Uncaught Error: Cannot find module '../build/Release/canvas.node' + // Require stack: + // - /.../deno/npm/registry.npmjs.org/canvas/2.11.2/lib/bindings.js + // - /.../.cache/deno/npm/registry.npmjs.org/canvas/2.11.2/lib/canvas.js + // ``` + } else if msg.contains("Cannot find module") + && msg.contains("Require stack") + && msg.contains(".node'") + { + return vec![ + FixSuggestion::info_multiline( + &[ + "Trying to execute an npm package using Node-API addons,", + "these packages require local `node_modules` directory to be present." + ] + ), + FixSuggestion::hint_multiline( + &[ + "Add `\"nodeModulesDir\": \"auto\" option to `deno.json`, and then run", + "`deno install --allow-scripts=npm:<package> --entrypoint <script>` to setup `node_modules` directory." + ] + ) + ]; + } else if msg.contains("document is not defined") { + return vec![ + FixSuggestion::info(cstr!( + "<u>document</> global is not available in Deno." + )), + FixSuggestion::hint_multiline(&[ + cstr!("Use a library like <u>happy-dom</>, <u>deno_dom</>, <u>linkedom</> or <u>JSDom</>"), + cstr!("and setup the <u>document</> global according to the library documentation."), + ]), + ]; + } + } - format_js_error_inner(js_error, circular, true, vec![]) + vec![] } -/// Format a [`JsError`] for terminal output, printing additional suggestions. -pub fn format_js_error_with_suggestions( - js_error: &JsError, - suggestions: Vec<FixSuggestion>, -) -> String { +/// Format a [`JsError`] for terminal output. +pub fn format_js_error(js_error: &JsError) -> String { let circular = find_recursive_cause(js_error).map(|reference| IndexedErrorReference { reference, index: 1, }); - + let suggestions = get_suggestions_for_terminal_errors(js_error); format_js_error_inner(js_error, circular, true, suggestions) } diff --git a/runtime/inspector_server.rs b/runtime/inspector_server.rs index 1f8cd5e71..a789dd3dc 100644 --- a/runtime/inspector_server.rs +++ b/runtime/inspector_server.rs @@ -19,6 +19,8 @@ use deno_core::serde_json::Value; use deno_core::unsync::spawn; use deno_core::url::Url; use deno_core::InspectorMsg; +use deno_core::InspectorSessionKind; +use deno_core::InspectorSessionOptions; use deno_core::InspectorSessionProxy; use deno_core::JsRuntime; use fastwebsockets::Frame; @@ -192,6 +194,11 @@ fn handle_ws_request( let inspector_session_proxy = InspectorSessionProxy { tx: outbound_tx, rx: inbound_rx, + options: InspectorSessionOptions { + kind: InspectorSessionKind::NonBlocking { + wait_for_disconnect: true, + }, + }, }; log::info!("Debugger session started."); diff --git a/runtime/js/40_fs_events.js b/runtime/js/40_fs_events.js index ec2474c0a..322ee6b3c 100644 --- a/runtime/js/40_fs_events.js +++ b/runtime/js/40_fs_events.js @@ -21,7 +21,7 @@ class FsWatcher { constructor(paths, options) { const { recursive } = options; - this.#rid = op_fs_events_open({ recursive, paths }); + this.#rid = op_fs_events_open(recursive, paths); } unref() { diff --git a/runtime/js/90_deno_ns.js b/runtime/js/90_deno_ns.js index fd2ac00f2..6300f599d 100644 --- a/runtime/js/90_deno_ns.js +++ b/runtime/js/90_deno_ns.js @@ -29,6 +29,7 @@ import * as tty from "ext:runtime/40_tty.js"; import * as kv from "ext:deno_kv/01_db.ts"; import * as cron from "ext:deno_cron/01_cron.ts"; import * as webgpuSurface from "ext:deno_webgpu/02_surface.js"; +import * as telemetry from "ext:runtime/telemetry.ts"; const denoNs = { Process: process.Process, @@ -134,7 +135,7 @@ const denoNs = { createHttpClient: httpClient.createHttpClient, }; -// NOTE(bartlomieju): keep IDs in sync with `cli/main.rs` +// NOTE(bartlomieju): keep IDs in sync with `runtime/lib.rs` const unstableIds = { broadcastChannel: 1, cron: 2, @@ -143,11 +144,13 @@ const unstableIds = { http: 5, kv: 6, net: 7, - process: 8, - temporal: 9, - unsafeProto: 10, - webgpu: 11, - workerOptions: 12, + nodeGlobals: 8, + otel: 9, + process: 10, + temporal: 11, + unsafeProto: 12, + webgpu: 13, + workerOptions: 14, }; const denoNsUnstableById = { __proto__: null }; @@ -181,4 +184,8 @@ denoNsUnstableById[unstableIds.webgpu] = { // denoNsUnstableById[unstableIds.workerOptions] = { __proto__: null } +denoNsUnstableById[unstableIds.otel] = { + telemetry: telemetry.telemetry, +}; + export { denoNs, denoNsUnstableById, unstableIds }; diff --git a/runtime/js/98_global_scope_shared.js b/runtime/js/98_global_scope_shared.js index 7a2723899..f8e76b7ce 100644 --- a/runtime/js/98_global_scope_shared.js +++ b/runtime/js/98_global_scope_shared.js @@ -32,6 +32,8 @@ import { DOMException } from "ext:deno_web/01_dom_exception.js"; import * as abortSignal from "ext:deno_web/03_abort_signal.js"; import * as imageData from "ext:deno_web/16_image_data.js"; import process from "node:process"; +import Buffer from "node:buffer"; +import { clearImmediate, setImmediate } from "node:timers"; import { loadWebGPU } from "ext:deno_webgpu/00_init.js"; import * as webgpuSurface from "ext:deno_webgpu/02_surface.js"; import { unstableIds } from "ext:runtime/90_deno_ns.js"; @@ -300,4 +302,15 @@ unstableForWindowOrWorkerGlobalScope[unstableIds.net] = { unstableForWindowOrWorkerGlobalScope[unstableIds.webgpu] = {}; +unstableForWindowOrWorkerGlobalScope[unstableIds.nodeGlobals] = { + Buffer: core.propWritable(Buffer), + setImmediate: core.propWritable(setImmediate), + clearImmediate: core.propWritable(clearImmediate), + global: { + enumerable: true, + configurable: true, + get: () => globalThis, + }, +}; + export { unstableForWindowOrWorkerGlobalScope, windowOrWorkerGlobalScope }; diff --git a/runtime/js/99_main.js b/runtime/js/99_main.js index 56a5b411b..eedca3396 100644 --- a/runtime/js/99_main.js +++ b/runtime/js/99_main.js @@ -27,7 +27,6 @@ const { ArrayPrototypeForEach, ArrayPrototypeIncludes, ArrayPrototypeMap, - DateNow, Error, ErrorPrototype, FunctionPrototypeBind, @@ -87,6 +86,8 @@ import { workerRuntimeGlobalProperties, } from "ext:runtime/98_global_scope_worker.js"; import { SymbolDispose, SymbolMetadata } from "ext:deno_web/00_infra.js"; +import { bootstrap as bootstrapOtel } from "ext:runtime/telemetry.ts"; + // deno-lint-ignore prefer-primordials if (Symbol.metadata) { throw "V8 supports Symbol.metadata now, no need to shim it"; @@ -470,6 +471,8 @@ const NOT_IMPORTED_OPS = [ // Related to `Deno.jupyter` API "op_jupyter_broadcast", "op_jupyter_input", + // Used in jupyter API + "op_base64_encode", // Related to `Deno.test()` API "op_test_event_step_result_failed", @@ -574,6 +577,7 @@ function bootstrapMainRuntime(runtimeOptions, warmup = false) { 10: serveHost, 11: serveIsMain, 12: serveWorkerCount, + 13: otelConfig, } = runtimeOptions; if (mode === executionModes.serve) { @@ -642,7 +646,7 @@ function bootstrapMainRuntime(runtimeOptions, warmup = false) { removeImportedOps(); - performance.setTimeOrigin(DateNow()); + performance.setTimeOrigin(); globalThis_ = globalThis; // Remove bootstrapping data from the global scope @@ -674,9 +678,10 @@ function bootstrapMainRuntime(runtimeOptions, warmup = false) { }); ObjectSetPrototypeOf(globalThis, Window.prototype); + bootstrapOtel(otelConfig); + if (inspectFlag) { - const consoleFromDeno = globalThis.console; - core.wrapConsole(consoleFromDeno, core.v8Console); + core.wrapConsole(globalThis.console, core.v8Console); } event.defineEventHandler(globalThis, "error"); @@ -696,6 +701,7 @@ function bootstrapMainRuntime(runtimeOptions, warmup = false) { // are lost. let jupyterNs = undefined; ObjectDefineProperty(finalDenoNs, "jupyter", { + __proto__: null, get() { if (jupyterNs) { return jupyterNs; @@ -855,9 +861,10 @@ function bootstrapWorkerRuntime( 5: hasNodeModulesDir, 6: argv0, 7: nodeDebug, + 13: otelConfig, } = runtimeOptions; - performance.setTimeOrigin(DateNow()); + performance.setTimeOrigin(); globalThis_ = globalThis; // Remove bootstrapping data from the global scope @@ -882,8 +889,9 @@ function bootstrapWorkerRuntime( } ObjectSetPrototypeOf(globalThis, DedicatedWorkerGlobalScope.prototype); - const consoleFromDeno = globalThis.console; - core.wrapConsole(consoleFromDeno, core.v8Console); + bootstrapOtel(otelConfig); + + core.wrapConsole(globalThis.console, core.v8Console); event.defineEventHandler(self, "message"); event.defineEventHandler(self, "error", undefined, true); diff --git a/runtime/js/telemetry.ts b/runtime/js/telemetry.ts new file mode 100644 index 000000000..ecef3b5e6 --- /dev/null +++ b/runtime/js/telemetry.ts @@ -0,0 +1,720 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. + +import { core, primordials } from "ext:core/mod.js"; +import { + op_crypto_get_random_values, + op_otel_instrumentation_scope_create_and_enter, + op_otel_instrumentation_scope_enter, + op_otel_instrumentation_scope_enter_builtin, + op_otel_log, + op_otel_span_attribute, + op_otel_span_attribute2, + op_otel_span_attribute3, + op_otel_span_continue, + op_otel_span_flush, + op_otel_span_set_dropped, + op_otel_span_start, +} from "ext:core/ops"; +import { Console } from "ext:deno_console/01_console.js"; +import { performance } from "ext:deno_web/15_performance.js"; + +const { + SafeWeakMap, + Array, + ObjectEntries, + SafeMap, + ReflectApply, + SymbolFor, + Error, + Uint8Array, + TypedArrayPrototypeSubarray, + ObjectAssign, + ObjectDefineProperty, + WeakRefPrototypeDeref, + String, + ObjectPrototypeIsPrototypeOf, + DataView, + DataViewPrototypeSetUint32, + SafeWeakRef, + TypedArrayPrototypeGetBuffer, +} = primordials; +const { AsyncVariable, setAsyncContext } = core; + +let TRACING_ENABLED = false; +let DETERMINISTIC = false; + +enum SpanKind { + INTERNAL = 0, + SERVER = 1, + CLIENT = 2, + PRODUCER = 3, + CONSUMER = 4, +} + +interface TraceState { + set(key: string, value: string): TraceState; + unset(key: string): TraceState; + get(key: string): string | undefined; + serialize(): string; +} + +interface SpanContext { + traceId: string; + spanId: string; + isRemote?: boolean; + traceFlags: number; + traceState?: TraceState; +} + +type HrTime = [number, number]; + +enum SpanStatusCode { + UNSET = 0, + OK = 1, + ERROR = 2, +} + +interface SpanStatus { + code: SpanStatusCode; + message?: string; +} + +export type AttributeValue = + | string + | number + | boolean + | Array<null | undefined | string> + | Array<null | undefined | number> + | Array<null | undefined | boolean>; + +interface Attributes { + [attributeKey: string]: AttributeValue | undefined; +} + +type SpanAttributes = Attributes; + +interface Link { + context: SpanContext; + attributes?: SpanAttributes; + droppedAttributesCount?: number; +} + +interface TimedEvent { + time: HrTime; + name: string; + attributes?: SpanAttributes; + droppedAttributesCount?: number; +} + +interface IArrayValue { + values: IAnyValue[]; +} + +interface IAnyValue { + stringValue?: string | null; + boolValue?: boolean | null; + intValue?: number | null; + doubleValue?: number | null; + arrayValue?: IArrayValue; + kvlistValue?: IKeyValueList; + bytesValue?: Uint8Array; +} + +interface IKeyValueList { + values: IKeyValue[]; +} + +interface IKeyValue { + key: string; + value: IAnyValue; +} +interface IResource { + attributes: IKeyValue[]; + droppedAttributesCount: number; +} + +interface InstrumentationLibrary { + readonly name: string; + readonly version?: string; + readonly schemaUrl?: string; +} + +interface ReadableSpan { + readonly name: string; + readonly kind: SpanKind; + readonly spanContext: () => SpanContext; + readonly parentSpanId?: string; + readonly startTime: HrTime; + readonly endTime: HrTime; + readonly status: SpanStatus; + readonly attributes: SpanAttributes; + readonly links: Link[]; + readonly events: TimedEvent[]; + readonly duration: HrTime; + readonly ended: boolean; + readonly resource: IResource; + readonly instrumentationLibrary: InstrumentationLibrary; + readonly droppedAttributesCount: number; + readonly droppedEventsCount: number; + readonly droppedLinksCount: number; +} + +enum ExportResultCode { + SUCCESS = 0, + FAILED = 1, +} + +interface ExportResult { + code: ExportResultCode; + error?: Error; +} + +function hrToSecs(hr: [number, number]): number { + return ((hr[0] * 1e3 + hr[1] / 1e6) / 1000); +} + +const TRACE_FLAG_SAMPLED = 1 << 0; + +const instrumentationScopes = new SafeWeakMap< + InstrumentationLibrary, + { __key: "instrumentation-library" } +>(); +let activeInstrumentationLibrary: WeakRef<InstrumentationLibrary> | null = null; + +function submit( + spanId: string | Uint8Array, + traceId: string | Uint8Array, + traceFlags: number, + parentSpanId: string | Uint8Array | null, + span: Omit< + ReadableSpan, + | "spanContext" + | "startTime" + | "endTime" + | "parentSpanId" + | "duration" + | "ended" + | "resource" + >, + startTime: number, + endTime: number, +) { + if (!(traceFlags & TRACE_FLAG_SAMPLED)) return; + + // TODO(@lucacasonato): `resource` is ignored for now, should we implement it? + + const instrumentationLibrary = span.instrumentationLibrary; + if ( + !activeInstrumentationLibrary || + WeakRefPrototypeDeref(activeInstrumentationLibrary) !== + instrumentationLibrary + ) { + activeInstrumentationLibrary = new SafeWeakRef(instrumentationLibrary); + if (instrumentationLibrary === BUILTIN_INSTRUMENTATION_LIBRARY) { + op_otel_instrumentation_scope_enter_builtin(); + } else { + let instrumentationScope = instrumentationScopes + .get(instrumentationLibrary); + + if (instrumentationScope === undefined) { + instrumentationScope = op_otel_instrumentation_scope_create_and_enter( + instrumentationLibrary.name, + instrumentationLibrary.version, + instrumentationLibrary.schemaUrl, + ) as { __key: "instrumentation-library" }; + instrumentationScopes.set( + instrumentationLibrary, + instrumentationScope, + ); + } else { + op_otel_instrumentation_scope_enter( + instrumentationScope, + ); + } + } + } + + op_otel_span_start( + traceId, + spanId, + parentSpanId, + span.kind, + span.name, + startTime, + endTime, + ); + + const status = span.status; + if (status !== null && status.code !== 0) { + op_otel_span_continue(status.code, status.message ?? ""); + } + + const attributeKvs = ObjectEntries(span.attributes); + let i = 0; + while (i < attributeKvs.length) { + if (i + 2 < attributeKvs.length) { + op_otel_span_attribute3( + attributeKvs.length, + attributeKvs[i][0], + attributeKvs[i][1], + attributeKvs[i + 1][0], + attributeKvs[i + 1][1], + attributeKvs[i + 2][0], + attributeKvs[i + 2][1], + ); + i += 3; + } else if (i + 1 < attributeKvs.length) { + op_otel_span_attribute2( + attributeKvs.length, + attributeKvs[i][0], + attributeKvs[i][1], + attributeKvs[i + 1][0], + attributeKvs[i + 1][1], + ); + i += 2; + } else { + op_otel_span_attribute( + attributeKvs.length, + attributeKvs[i][0], + attributeKvs[i][1], + ); + i += 1; + } + } + + // TODO(@lucacasonato): implement links + // TODO(@lucacasonato): implement events + + const droppedAttributesCount = span.droppedAttributesCount; + const droppedLinksCount = span.droppedLinksCount + span.links.length; + const droppedEventsCount = span.droppedEventsCount + span.events.length; + if ( + droppedAttributesCount > 0 || droppedLinksCount > 0 || + droppedEventsCount > 0 + ) { + op_otel_span_set_dropped( + droppedAttributesCount, + droppedLinksCount, + droppedEventsCount, + ); + } + + op_otel_span_flush(); +} + +const now = () => (performance.timeOrigin + performance.now()) / 1000; + +const SPAN_ID_BYTES = 8; +const TRACE_ID_BYTES = 16; + +const INVALID_TRACE_ID = new Uint8Array(TRACE_ID_BYTES); +const INVALID_SPAN_ID = new Uint8Array(SPAN_ID_BYTES); + +const NO_ASYNC_CONTEXT = {}; + +let otelLog: (message: string, level: number) => void; + +const hexSliceLookupTable = (function () { + const alphabet = "0123456789abcdef"; + const table = new Array(256); + for (let i = 0; i < 16; ++i) { + const i16 = i * 16; + for (let j = 0; j < 16; ++j) { + table[i16 + j] = alphabet[i] + alphabet[j]; + } + } + return table; +})(); + +function bytesToHex(bytes: Uint8Array): string { + let out = ""; + for (let i = 0; i < bytes.length; i += 1) { + out += hexSliceLookupTable[bytes[i]]; + } + return out; +} + +const SPAN_KEY = SymbolFor("OpenTelemetry Context Key SPAN"); + +const BUILTIN_INSTRUMENTATION_LIBRARY: InstrumentationLibrary = {} as never; + +let COUNTER = 1; + +export let enterSpan: (span: Span) => void; +export let exitSpan: (span: Span) => void; +export let endSpan: (span: Span) => void; + +export class Span { + #traceId: string | Uint8Array; + #spanId: Uint8Array; + #traceFlags = TRACE_FLAG_SAMPLED; + + #spanContext: SpanContext | null = null; + + #parentSpanId: string | Uint8Array | null = null; + #parentSpanIdString: string | null = null; + + #recording = TRACING_ENABLED; + + #kind: number = 0; + #name: string; + #startTime: number; + #status: { code: number; message?: string } | null = null; + #attributes: Attributes = { __proto__: null } as never; + + #droppedEventsCount = 0; + #droppedLinksCount = 0; + + #asyncContext = NO_ASYNC_CONTEXT; + + static { + otelLog = function otelLog(message, level) { + let traceId = null; + let spanId = null; + let traceFlags = 0; + const span = CURRENT.get()?.getValue(SPAN_KEY); + if (span) { + // The lint is wrong, we can not use anything but `in` here because this + // is a private field. + // deno-lint-ignore prefer-primordials + if (#traceId in span) { + traceId = span.#traceId; + spanId = span.#spanId; + traceFlags = span.#traceFlags; + } else { + const context = span.spanContext(); + traceId = context.traceId; + spanId = context.spanId; + traceFlags = context.traceFlags; + } + } + return op_otel_log(message, level, traceId, spanId, traceFlags); + }; + + enterSpan = (span: Span) => { + if (!span.#recording) return; + const context = (CURRENT.get() || ROOT_CONTEXT).setValue(SPAN_KEY, span); + span.#asyncContext = CURRENT.enter(context); + }; + + exitSpan = (span: Span) => { + if (!span.#recording) return; + if (span.#asyncContext === NO_ASYNC_CONTEXT) return; + setAsyncContext(span.#asyncContext); + span.#asyncContext = NO_ASYNC_CONTEXT; + }; + + exitSpan = (span: Span) => { + const endTime = now(); + submit( + span.#spanId, + span.#traceId, + span.#traceFlags, + span.#parentSpanId, + { + name: span.#name, + kind: span.#kind, + status: span.#status ?? { code: 0 }, + attributes: span.#attributes, + events: [], + links: [], + droppedAttributesCount: 0, + droppedEventsCount: span.#droppedEventsCount, + droppedLinksCount: span.#droppedLinksCount, + instrumentationLibrary: BUILTIN_INSTRUMENTATION_LIBRARY, + }, + span.#startTime, + endTime, + ); + }; + } + + constructor( + name: string, + attributes?: Attributes, + ) { + if (!this.isRecording) { + this.#name = ""; + this.#startTime = 0; + this.#traceId = INVALID_TRACE_ID; + this.#spanId = INVALID_SPAN_ID; + this.#traceFlags = 0; + return; + } + + this.#name = name; + this.#startTime = now(); + this.#attributes = attributes ?? { __proto__: null } as never; + + const currentSpan: Span | { + spanContext(): { traceId: string; spanId: string }; + } = CURRENT.get()?.getValue(SPAN_KEY); + if (!currentSpan) { + const buffer = new Uint8Array(TRACE_ID_BYTES + SPAN_ID_BYTES); + if (DETERMINISTIC) { + DataViewPrototypeSetUint32( + new DataView(TypedArrayPrototypeGetBuffer(buffer)), + TRACE_ID_BYTES - 4, + COUNTER, + true, + ); + COUNTER += 1; + DataViewPrototypeSetUint32( + new DataView(TypedArrayPrototypeGetBuffer(buffer)), + TRACE_ID_BYTES + SPAN_ID_BYTES - 4, + COUNTER, + true, + ); + COUNTER += 1; + } else { + op_crypto_get_random_values(buffer); + } + this.#traceId = TypedArrayPrototypeSubarray(buffer, 0, TRACE_ID_BYTES); + this.#spanId = TypedArrayPrototypeSubarray(buffer, TRACE_ID_BYTES); + } else { + this.#spanId = new Uint8Array(SPAN_ID_BYTES); + if (DETERMINISTIC) { + DataViewPrototypeSetUint32( + new DataView(TypedArrayPrototypeGetBuffer(this.#spanId)), + SPAN_ID_BYTES - 4, + COUNTER, + true, + ); + COUNTER += 1; + } else { + op_crypto_get_random_values(this.#spanId); + } + // deno-lint-ignore prefer-primordials + if (#traceId in currentSpan) { + this.#traceId = currentSpan.#traceId; + this.#parentSpanId = currentSpan.#spanId; + } else { + const context = currentSpan.spanContext(); + this.#traceId = context.traceId; + this.#parentSpanId = context.spanId; + } + } + } + + spanContext() { + if (!this.#spanContext) { + this.#spanContext = { + traceId: typeof this.#traceId === "string" + ? this.#traceId + : bytesToHex(this.#traceId), + spanId: typeof this.#spanId === "string" + ? this.#spanId + : bytesToHex(this.#spanId), + traceFlags: this.#traceFlags, + }; + } + return this.#spanContext; + } + + get parentSpanId() { + if (!this.#parentSpanIdString && this.#parentSpanId) { + if (typeof this.#parentSpanId === "string") { + this.#parentSpanIdString = this.#parentSpanId; + } else { + this.#parentSpanIdString = bytesToHex(this.#parentSpanId); + } + } + return this.#parentSpanIdString; + } + + setAttribute(name: string, value: AttributeValue) { + if (this.#recording) this.#attributes[name] = value; + return this; + } + + setAttributes(attributes: Attributes) { + if (this.#recording) ObjectAssign(this.#attributes, attributes); + return this; + } + + setStatus(status: { code: number; message?: string }) { + if (this.#recording) { + if (status.code === 0) { + this.#status = null; + } else if (status.code > 2) { + throw new Error("Invalid status code"); + } else { + this.#status = status; + } + } + return this; + } + + updateName(name: string) { + if (this.#recording) this.#name = name; + return this; + } + + addEvent(_name: never) { + // TODO(@lucacasonato): implement events + if (this.#recording) this.#droppedEventsCount += 1; + return this; + } + + addLink(_link: never) { + // TODO(@lucacasonato): implement links + if (this.#recording) this.#droppedLinksCount += 1; + return this; + } + + addLinks(links: never[]) { + // TODO(@lucacasonato): implement links + if (this.#recording) this.#droppedLinksCount += links.length; + return this; + } + + isRecording() { + return this.#recording; + } +} + +// Exporter compatible with opentelemetry js library +class SpanExporter { + export( + spans: ReadableSpan[], + resultCallback: (result: ExportResult) => void, + ) { + try { + for (let i = 0; i < spans.length; i += 1) { + const span = spans[i]; + const context = span.spanContext(); + submit( + context.spanId, + context.traceId, + context.traceFlags, + span.parentSpanId ?? null, + span, + hrToSecs(span.startTime), + hrToSecs(span.endTime), + ); + } + resultCallback({ code: 0 }); + } catch (error) { + resultCallback({ + code: 1, + error: ObjectPrototypeIsPrototypeOf(error, Error) + ? error as Error + : new Error(String(error)), + }); + } + } + + async shutdown() {} + + async forceFlush() {} +} + +const CURRENT = new AsyncVariable(); + +class Context { + #data = new SafeMap(); + + // deno-lint-ignore no-explicit-any + constructor(data?: Iterable<readonly [any, any]> | null | undefined) { + this.#data = data ? new SafeMap(data) : new SafeMap(); + } + + getValue(key: symbol): unknown { + return this.#data.get(key); + } + + setValue(key: symbol, value: unknown): Context { + const c = new Context(this.#data); + c.#data.set(key, value); + return c; + } + + deleteValue(key: symbol): Context { + const c = new Context(this.#data); + c.#data.delete(key); + return c; + } +} + +// TODO(lucacasonato): @opentelemetry/api defines it's own ROOT_CONTEXT +const ROOT_CONTEXT = new Context(); + +// Context manager for opentelemetry js library +class ContextManager { + active(): Context { + return CURRENT.get() ?? ROOT_CONTEXT; + } + + with<A extends unknown[], F extends (...args: A) => ReturnType<F>>( + context: Context, + fn: F, + thisArg?: ThisParameterType<F>, + ...args: A + ): ReturnType<F> { + const ctx = CURRENT.enter(context); + try { + return ReflectApply(fn, thisArg, args); + } finally { + setAsyncContext(ctx); + } + } + + // deno-lint-ignore no-explicit-any + bind<T extends (...args: any[]) => any>( + context: Context, + target: T, + ): T { + return ((...args) => { + const ctx = CURRENT.enter(context); + try { + return ReflectApply(target, this, args); + } finally { + setAsyncContext(ctx); + } + }) as T; + } + + enable() { + return this; + } + + disable() { + return this; + } +} + +const otelConsoleConfig = { + ignore: 0, + capture: 1, + replace: 2, +}; + +export function bootstrap( + config: [] | [ + typeof otelConsoleConfig[keyof typeof otelConsoleConfig], + number, + ], +): void { + if (config.length === 0) return; + const { 0: consoleConfig, 1: deterministic } = config; + + TRACING_ENABLED = true; + DETERMINISTIC = deterministic === 1; + + switch (consoleConfig) { + case otelConsoleConfig.capture: + core.wrapConsole(globalThis.console, new Console(otelLog)); + break; + case otelConsoleConfig.replace: + ObjectDefineProperty( + globalThis, + "console", + core.propNonEnumerable(new Console(otelLog)), + ); + break; + default: + break; + } +} + +export const telemetry = { SpanExporter, ContextManager }; diff --git a/runtime/lib.rs b/runtime/lib.rs index f0b1129ce..a6e60ced1 100644 --- a/runtime/lib.rs +++ b/runtime/lib.rs @@ -35,6 +35,7 @@ pub mod js; pub mod ops; pub mod permissions; pub mod snapshot; +pub mod sys_info; pub mod tokio_util; pub mod web_worker; pub mod worker; @@ -99,18 +100,30 @@ pub static UNSTABLE_GRANULAR_FLAGS: &[UnstableGranularFlag] = &[ show_in_help: true, id: 7, }, + UnstableGranularFlag { + name: "node-globals", + help_text: "Expose Node globals everywhere", + show_in_help: true, + id: 8, + }, + UnstableGranularFlag { + name: "otel", + help_text: "Enable unstable OpenTelemetry features", + show_in_help: false, + id: 9, + }, // TODO(bartlomieju): consider removing it UnstableGranularFlag { name: ops::process::UNSTABLE_FEATURE_NAME, help_text: "Enable unstable process APIs", show_in_help: false, - id: 8, + id: 10, }, UnstableGranularFlag { name: "temporal", help_text: "Enable unstable Temporal API", show_in_help: true, - id: 9, + id: 11, }, UnstableGranularFlag { name: "unsafe-proto", @@ -118,22 +131,28 @@ pub static UNSTABLE_GRANULAR_FLAGS: &[UnstableGranularFlag] = &[ show_in_help: true, // This number is used directly in the JS code. Search // for "unstableIds" to see where it's used. - id: 10, + id: 12, }, UnstableGranularFlag { name: deno_webgpu::UNSTABLE_FEATURE_NAME, help_text: "Enable unstable `WebGPU` APIs", show_in_help: true, - id: 11, + id: 13, }, UnstableGranularFlag { name: ops::worker_host::UNSTABLE_FEATURE_NAME, help_text: "Enable unstable Web Worker APIs", show_in_help: true, - id: 12, + id: 14, }, ]; +pub fn exit(code: i32) -> ! { + crate::ops::otel::flush(); + #[allow(clippy::disallowed_methods)] + std::process::exit(code); +} + #[cfg(test)] mod test { use super::*; diff --git a/runtime/ops/fs_events.rs b/runtime/ops/fs_events.rs index d88a32d91..c8e0228bc 100644 --- a/runtime/ops/fs_events.rs +++ b/runtime/ops/fs_events.rs @@ -1,6 +1,5 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -use deno_core::error::AnyError; use deno_core::parking_lot::Mutex; use deno_core::AsyncRefCell; use deno_core::CancelFuture; @@ -20,13 +19,14 @@ use notify::EventKind; use notify::RecommendedWatcher; use notify::RecursiveMode; use notify::Watcher; -use serde::Deserialize; use serde::Serialize; use std::borrow::Cow; use std::cell::RefCell; use std::convert::From; +use std::path::Path; use std::path::PathBuf; use std::rc::Rc; +use std::sync::Arc; use tokio::sync::mpsc; deno_core::extension!( @@ -35,9 +35,7 @@ deno_core::extension!( ); struct FsEventsResource { - #[allow(unused)] - watcher: RecommendedWatcher, - receiver: AsyncRefCell<mpsc::Receiver<Result<FsEvent, AnyError>>>, + receiver: AsyncRefCell<mpsc::Receiver<Result<FsEvent, NotifyError>>>, cancel: CancelHandle, } @@ -59,7 +57,7 @@ impl Resource for FsEventsResource { /// /// Feel free to expand this struct as long as you can add tests to demonstrate /// the complexity. -#[derive(Serialize, Debug)] +#[derive(Serialize, Debug, Clone)] struct FsEvent { kind: &'static str, paths: Vec<PathBuf>, @@ -93,43 +91,102 @@ impl From<NotifyEvent> for FsEvent { } } -#[derive(Deserialize)] -pub struct OpenArgs { - recursive: bool, - paths: Vec<String>, +type WatchSender = (Vec<String>, mpsc::Sender<Result<FsEvent, NotifyError>>); + +struct WatcherState { + senders: Arc<Mutex<Vec<WatchSender>>>, + watcher: RecommendedWatcher, } -#[op2] -#[smi] -fn op_fs_events_open( +fn starts_with_canonicalized(path: &Path, prefix: &str) -> bool { + #[allow(clippy::disallowed_methods)] + let path = path.canonicalize().ok(); + #[allow(clippy::disallowed_methods)] + let prefix = std::fs::canonicalize(prefix).ok(); + match (path, prefix) { + (Some(path), Some(prefix)) => path.starts_with(prefix), + _ => false, + } +} + +#[derive(Debug, thiserror::Error)] +pub enum FsEventsError { + #[error(transparent)] + Resource(deno_core::error::AnyError), + #[error(transparent)] + Permission(#[from] deno_permissions::PermissionCheckError), + #[error(transparent)] + Notify(#[from] NotifyError), + #[error(transparent)] + Canceled(#[from] deno_core::Canceled), +} + +fn start_watcher( state: &mut OpState, - #[serde] args: OpenArgs, -) -> Result<ResourceId, AnyError> { - let (sender, receiver) = mpsc::channel::<Result<FsEvent, AnyError>>(16); - let sender = Mutex::new(sender); - let mut watcher: RecommendedWatcher = Watcher::new( + paths: Vec<String>, + sender: mpsc::Sender<Result<FsEvent, NotifyError>>, +) -> Result<(), FsEventsError> { + if let Some(watcher) = state.try_borrow_mut::<WatcherState>() { + watcher.senders.lock().push((paths, sender)); + return Ok(()); + } + + let senders = Arc::new(Mutex::new(vec![(paths, sender)])); + + let sender_clone = senders.clone(); + let watcher: RecommendedWatcher = Watcher::new( move |res: Result<NotifyEvent, NotifyError>| { - let res2 = res.map(FsEvent::from).map_err(AnyError::from); - let sender = sender.lock(); - // Ignore result, if send failed it means that watcher was already closed, - // but not all messages have been flushed. - let _ = sender.try_send(res2); + let res2 = res.map(FsEvent::from).map_err(FsEventsError::Notify); + for (paths, sender) in sender_clone.lock().iter() { + // Ignore result, if send failed it means that watcher was already closed, + // but not all messages have been flushed. + + // Only send the event if the path matches one of the paths that the user is watching + if let Ok(event) = &res2 { + if paths.iter().any(|path| { + event.paths.iter().any(|event_path| { + same_file::is_same_file(event_path, path).unwrap_or(false) + || starts_with_canonicalized(event_path, path) + }) + }) { + let _ = sender.try_send(Ok(event.clone())); + } + } + } }, Default::default(), )?; - let recursive_mode = if args.recursive { + + state.put::<WatcherState>(WatcherState { watcher, senders }); + + Ok(()) +} + +#[op2] +#[smi] +fn op_fs_events_open( + state: &mut OpState, + recursive: bool, + #[serde] paths: Vec<String>, +) -> Result<ResourceId, FsEventsError> { + let (sender, receiver) = mpsc::channel::<Result<FsEvent, NotifyError>>(16); + + start_watcher(state, paths.clone(), sender)?; + + let recursive_mode = if recursive { RecursiveMode::Recursive } else { RecursiveMode::NonRecursive }; - for path in &args.paths { + for path in &paths { let path = state .borrow_mut::<PermissionsContainer>() .check_read(path, "Deno.watchFs()")?; - watcher.watch(&path, recursive_mode)?; + + let watcher = state.borrow_mut::<WatcherState>(); + watcher.watcher.watch(&path, recursive_mode)?; } let resource = FsEventsResource { - watcher, receiver: AsyncRefCell::new(receiver), cancel: Default::default(), }; @@ -142,14 +199,18 @@ fn op_fs_events_open( async fn op_fs_events_poll( state: Rc<RefCell<OpState>>, #[smi] rid: ResourceId, -) -> Result<Option<FsEvent>, AnyError> { - let resource = state.borrow().resource_table.get::<FsEventsResource>(rid)?; +) -> Result<Option<FsEvent>, FsEventsError> { + let resource = state + .borrow() + .resource_table + .get::<FsEventsResource>(rid) + .map_err(FsEventsError::Resource)?; let mut receiver = RcRef::map(&resource, |r| &r.receiver).borrow_mut().await; let cancel = RcRef::map(resource, |r| &r.cancel); let maybe_result = receiver.recv().or_cancel(cancel).await?; match maybe_result { Some(Ok(value)) => Ok(Some(value)), - Some(Err(err)) => Err(err), + Some(Err(err)) => Err(FsEventsError::Notify(err)), None => Ok(None), } } diff --git a/runtime/ops/http.rs b/runtime/ops/http.rs index cec8b0ef8..6e3157668 100644 --- a/runtime/ops/http.rs +++ b/runtime/ops/http.rs @@ -2,9 +2,6 @@ use std::rc::Rc; -use deno_core::error::bad_resource_id; -use deno_core::error::custom_error; -use deno_core::error::AnyError; use deno_core::op2; use deno_core::OpState; use deno_core::ResourceId; @@ -16,12 +13,31 @@ pub const UNSTABLE_FEATURE_NAME: &str = "http"; deno_core::extension!(deno_http_runtime, ops = [op_http_start],); +#[derive(Debug, thiserror::Error)] +pub enum HttpStartError { + #[error("TCP stream is currently in use")] + TcpStreamInUse, + #[error("TLS stream is currently in use")] + TlsStreamInUse, + #[error("Unix socket is currently in use")] + UnixSocketInUse, + #[error(transparent)] + ReuniteTcp(#[from] tokio::net::tcp::ReuniteError), + #[cfg(unix)] + #[error(transparent)] + ReuniteUnix(#[from] tokio::net::unix::ReuniteError), + #[error("{0}")] + Io(#[from] std::io::Error), + #[error(transparent)] + Other(deno_core::error::AnyError), +} + #[op2(fast)] #[smi] fn op_http_start( state: &mut OpState, #[smi] tcp_stream_rid: ResourceId, -) -> Result<ResourceId, AnyError> { +) -> Result<ResourceId, HttpStartError> { if let Ok(resource_rc) = state .resource_table .take::<TcpStreamResource>(tcp_stream_rid) @@ -30,11 +46,11 @@ fn op_http_start( // process of starting a HTTP server on top of this TCP connection, so we just return a Busy error. // See also: https://github.com/denoland/deno/pull/16242 let resource = Rc::try_unwrap(resource_rc) - .map_err(|_| custom_error("Busy", "TCP stream is currently in use"))?; + .map_err(|_| HttpStartError::TcpStreamInUse)?; let (read_half, write_half) = resource.into_inner(); let tcp_stream = read_half.reunite(write_half)?; let addr = tcp_stream.local_addr()?; - return http_create_conn_resource(state, tcp_stream, addr, "http"); + return Ok(http_create_conn_resource(state, tcp_stream, addr, "http")); } if let Ok(resource_rc) = state @@ -45,11 +61,11 @@ fn op_http_start( // process of starting a HTTP server on top of this TLS connection, so we just return a Busy error. // See also: https://github.com/denoland/deno/pull/16242 let resource = Rc::try_unwrap(resource_rc) - .map_err(|_| custom_error("Busy", "TLS stream is currently in use"))?; + .map_err(|_| HttpStartError::TlsStreamInUse)?; let (read_half, write_half) = resource.into_inner(); let tls_stream = read_half.unsplit(write_half); let addr = tls_stream.local_addr()?; - return http_create_conn_resource(state, tls_stream, addr, "https"); + return Ok(http_create_conn_resource(state, tls_stream, addr, "https")); } #[cfg(unix)] @@ -61,12 +77,17 @@ fn op_http_start( // process of starting a HTTP server on top of this UNIX socket, so we just return a Busy error. // See also: https://github.com/denoland/deno/pull/16242 let resource = Rc::try_unwrap(resource_rc) - .map_err(|_| custom_error("Busy", "Unix socket is currently in use"))?; + .map_err(|_| HttpStartError::UnixSocketInUse)?; let (read_half, write_half) = resource.into_inner(); let unix_stream = read_half.reunite(write_half)?; let addr = unix_stream.local_addr()?; - return http_create_conn_resource(state, unix_stream, addr, "http+unix"); + return Ok(http_create_conn_resource( + state, + unix_stream, + addr, + "http+unix", + )); } - Err(bad_resource_id()) + Err(HttpStartError::Other(deno_core::error::bad_resource_id())) } diff --git a/runtime/ops/mod.rs b/runtime/ops/mod.rs index feed5052b..c2e402f33 100644 --- a/runtime/ops/mod.rs +++ b/runtime/ops/mod.rs @@ -4,12 +4,12 @@ pub mod bootstrap; pub mod fs_events; pub mod http; pub mod os; +pub mod otel; pub mod permissions; pub mod process; pub mod runtime; pub mod signal; pub mod tty; -mod utils; pub mod web_worker; pub mod worker_host; diff --git a/runtime/ops/os/mod.rs b/runtime/ops/os/mod.rs index bd9260e97..74c708c53 100644 --- a/runtime/ops/os/mod.rs +++ b/runtime/ops/os/mod.rs @@ -1,9 +1,7 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -use super::utils::into_string; +use crate::sys_info; use crate::worker::ExitCode; -use deno_core::error::type_error; -use deno_core::error::AnyError; use deno_core::op2; use deno_core::v8; use deno_core::OpState; @@ -14,8 +12,6 @@ use serde::Serialize; use std::collections::HashMap; use std::env; -mod sys_info; - deno_core::extension!( deno_os, ops = [ @@ -73,9 +69,27 @@ deno_core::extension!( }, ); +#[derive(Debug, thiserror::Error)] +pub enum OsError { + #[error(transparent)] + Permission(#[from] deno_permissions::PermissionCheckError), + #[error("File name or path {0:?} is not valid UTF-8")] + InvalidUtf8(std::ffi::OsString), + #[error("Key is an empty string.")] + EnvEmptyKey, + #[error("Key contains invalid characters: {0:?}")] + EnvInvalidKey(String), + #[error("Value contains invalid characters: {0:?}")] + EnvInvalidValue(String), + #[error(transparent)] + Var(#[from] env::VarError), + #[error("{0}")] + Io(#[from] std::io::Error), +} + #[op2] #[string] -fn op_exec_path(state: &mut OpState) -> Result<String, AnyError> { +fn op_exec_path(state: &mut OpState) -> Result<String, OsError> { let current_exe = env::current_exe().unwrap(); state .borrow_mut::<PermissionsContainer>() @@ -83,7 +97,10 @@ fn op_exec_path(state: &mut OpState) -> Result<String, AnyError> { // normalize path so it doesn't include '.' or '..' components let path = normalize_path(current_exe); - into_string(path.into_os_string()) + path + .into_os_string() + .into_string() + .map_err(OsError::InvalidUtf8) } #[op2(fast)] @@ -91,20 +108,16 @@ fn op_set_env( state: &mut OpState, #[string] key: &str, #[string] value: &str, -) -> Result<(), AnyError> { +) -> Result<(), OsError> { state.borrow_mut::<PermissionsContainer>().check_env(key)?; if key.is_empty() { - return Err(type_error("Key is an empty string.")); + return Err(OsError::EnvEmptyKey); } if key.contains(&['=', '\0'] as &[char]) { - return Err(type_error(format!( - "Key contains invalid characters: {key:?}" - ))); + return Err(OsError::EnvInvalidKey(key.to_string())); } if value.contains('\0') { - return Err(type_error(format!( - "Value contains invalid characters: {value:?}" - ))); + return Err(OsError::EnvInvalidValue(value.to_string())); } env::set_var(key, value); Ok(()) @@ -112,7 +125,9 @@ fn op_set_env( #[op2] #[serde] -fn op_env(state: &mut OpState) -> Result<HashMap<String, String>, AnyError> { +fn op_env( + state: &mut OpState, +) -> Result<HashMap<String, String>, deno_core::error::AnyError> { state.borrow_mut::<PermissionsContainer>().check_env_all()?; Ok(env::vars().collect()) } @@ -122,7 +137,7 @@ fn op_env(state: &mut OpState) -> Result<HashMap<String, String>, AnyError> { fn op_get_env( state: &mut OpState, #[string] key: String, -) -> Result<Option<String>, AnyError> { +) -> Result<Option<String>, OsError> { let skip_permission_check = NODE_ENV_VAR_ALLOWLIST.contains(&key); if !skip_permission_check { @@ -130,13 +145,11 @@ fn op_get_env( } if key.is_empty() { - return Err(type_error("Key is an empty string.")); + return Err(OsError::EnvEmptyKey); } if key.contains(&['=', '\0'] as &[char]) { - return Err(type_error(format!( - "Key contains invalid characters: {key:?}" - ))); + return Err(OsError::EnvInvalidKey(key.to_string())); } let r = match env::var(key) { @@ -150,10 +163,10 @@ fn op_get_env( fn op_delete_env( state: &mut OpState, #[string] key: String, -) -> Result<(), AnyError> { +) -> Result<(), OsError> { state.borrow_mut::<PermissionsContainer>().check_env(&key)?; if key.is_empty() || key.contains(&['=', '\0'] as &[char]) { - return Err(type_error("Key contains invalid characters.")); + return Err(OsError::EnvInvalidKey(key.to_string())); } env::remove_var(key); Ok(()) @@ -173,12 +186,14 @@ fn op_get_exit_code(state: &mut OpState) -> i32 { #[op2(fast)] fn op_exit(state: &mut OpState) { let code = state.borrow::<ExitCode>().get(); - std::process::exit(code) + crate::exit(code) } #[op2] #[serde] -fn op_loadavg(state: &mut OpState) -> Result<(f64, f64, f64), AnyError> { +fn op_loadavg( + state: &mut OpState, +) -> Result<(f64, f64, f64), deno_core::error::AnyError> { state .borrow_mut::<PermissionsContainer>() .check_sys("loadavg", "Deno.loadavg()")?; @@ -187,7 +202,9 @@ fn op_loadavg(state: &mut OpState) -> Result<(f64, f64, f64), AnyError> { #[op2] #[string] -fn op_hostname(state: &mut OpState) -> Result<String, AnyError> { +fn op_hostname( + state: &mut OpState, +) -> Result<String, deno_core::error::AnyError> { state .borrow_mut::<PermissionsContainer>() .check_sys("hostname", "Deno.hostname()")?; @@ -196,7 +213,9 @@ fn op_hostname(state: &mut OpState) -> Result<String, AnyError> { #[op2] #[string] -fn op_os_release(state: &mut OpState) -> Result<String, AnyError> { +fn op_os_release( + state: &mut OpState, +) -> Result<String, deno_core::error::AnyError> { state .borrow_mut::<PermissionsContainer>() .check_sys("osRelease", "Deno.osRelease()")?; @@ -207,7 +226,7 @@ fn op_os_release(state: &mut OpState) -> Result<String, AnyError> { #[serde] fn op_network_interfaces( state: &mut OpState, -) -> Result<Vec<NetworkInterface>, AnyError> { +) -> Result<Vec<NetworkInterface>, OsError> { state .borrow_mut::<PermissionsContainer>() .check_sys("networkInterfaces", "Deno.networkInterfaces()")?; @@ -259,7 +278,7 @@ impl From<netif::Interface> for NetworkInterface { #[serde] fn op_system_memory_info( state: &mut OpState, -) -> Result<Option<sys_info::MemInfo>, AnyError> { +) -> Result<Option<sys_info::MemInfo>, deno_core::error::AnyError> { state .borrow_mut::<PermissionsContainer>() .check_sys("systemMemoryInfo", "Deno.systemMemoryInfo()")?; @@ -269,7 +288,9 @@ fn op_system_memory_info( #[cfg(not(windows))] #[op2] #[smi] -fn op_gid(state: &mut OpState) -> Result<Option<u32>, AnyError> { +fn op_gid( + state: &mut OpState, +) -> Result<Option<u32>, deno_core::error::AnyError> { state .borrow_mut::<PermissionsContainer>() .check_sys("gid", "Deno.gid()")?; @@ -283,7 +304,9 @@ fn op_gid(state: &mut OpState) -> Result<Option<u32>, AnyError> { #[cfg(windows)] #[op2] #[smi] -fn op_gid(state: &mut OpState) -> Result<Option<u32>, AnyError> { +fn op_gid( + state: &mut OpState, +) -> Result<Option<u32>, deno_core::error::AnyError> { state .borrow_mut::<PermissionsContainer>() .check_sys("gid", "Deno.gid()")?; @@ -293,7 +316,9 @@ fn op_gid(state: &mut OpState) -> Result<Option<u32>, AnyError> { #[cfg(not(windows))] #[op2] #[smi] -fn op_uid(state: &mut OpState) -> Result<Option<u32>, AnyError> { +fn op_uid( + state: &mut OpState, +) -> Result<Option<u32>, deno_core::error::AnyError> { state .borrow_mut::<PermissionsContainer>() .check_sys("uid", "Deno.uid()")?; @@ -307,7 +332,9 @@ fn op_uid(state: &mut OpState) -> Result<Option<u32>, AnyError> { #[cfg(windows)] #[op2] #[smi] -fn op_uid(state: &mut OpState) -> Result<Option<u32>, AnyError> { +fn op_uid( + state: &mut OpState, +) -> Result<Option<u32>, deno_core::error::AnyError> { state .borrow_mut::<PermissionsContainer>() .check_sys("uid", "Deno.uid()")?; @@ -485,7 +512,7 @@ fn rss() -> usize { } } -fn os_uptime(state: &mut OpState) -> Result<u64, AnyError> { +fn os_uptime(state: &mut OpState) -> Result<u64, deno_core::error::AnyError> { state .borrow_mut::<PermissionsContainer>() .check_sys("osUptime", "Deno.osUptime()")?; @@ -494,6 +521,8 @@ fn os_uptime(state: &mut OpState) -> Result<u64, AnyError> { #[op2(fast)] #[number] -fn op_os_uptime(state: &mut OpState) -> Result<u64, AnyError> { +fn op_os_uptime( + state: &mut OpState, +) -> Result<u64, deno_core::error::AnyError> { os_uptime(state) } diff --git a/runtime/ops/otel.rs b/runtime/ops/otel.rs new file mode 100644 index 000000000..61a7b0ef0 --- /dev/null +++ b/runtime/ops/otel.rs @@ -0,0 +1,855 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. + +use crate::tokio_util::create_basic_runtime; +use deno_core::anyhow; +use deno_core::anyhow::anyhow; +use deno_core::futures::channel::mpsc; +use deno_core::futures::channel::mpsc::UnboundedSender; +use deno_core::futures::future::BoxFuture; +use deno_core::futures::stream; +use deno_core::futures::Stream; +use deno_core::futures::StreamExt; +use deno_core::op2; +use deno_core::v8; +use deno_core::OpState; +use once_cell::sync::Lazy; +use once_cell::sync::OnceCell; +use opentelemetry::logs::AnyValue; +use opentelemetry::logs::LogRecord as LogRecordTrait; +use opentelemetry::logs::Severity; +use opentelemetry::trace::SpanContext; +use opentelemetry::trace::SpanId; +use opentelemetry::trace::SpanKind; +use opentelemetry::trace::Status as SpanStatus; +use opentelemetry::trace::TraceFlags; +use opentelemetry::trace::TraceId; +use opentelemetry::Key; +use opentelemetry::KeyValue; +use opentelemetry::StringValue; +use opentelemetry::Value; +use opentelemetry_otlp::HttpExporterBuilder; +use opentelemetry_otlp::Protocol; +use opentelemetry_otlp::WithExportConfig; +use opentelemetry_otlp::WithHttpConfig; +use opentelemetry_sdk::export::trace::SpanData; +use opentelemetry_sdk::logs::BatchLogProcessor; +use opentelemetry_sdk::logs::LogProcessor as LogProcessorTrait; +use opentelemetry_sdk::logs::LogRecord; +use opentelemetry_sdk::trace::BatchSpanProcessor; +use opentelemetry_sdk::trace::SpanProcessor as SpanProcessorTrait; +use opentelemetry_sdk::Resource; +use opentelemetry_semantic_conventions::resource::PROCESS_RUNTIME_NAME; +use opentelemetry_semantic_conventions::resource::PROCESS_RUNTIME_VERSION; +use opentelemetry_semantic_conventions::resource::TELEMETRY_SDK_LANGUAGE; +use opentelemetry_semantic_conventions::resource::TELEMETRY_SDK_NAME; +use opentelemetry_semantic_conventions::resource::TELEMETRY_SDK_VERSION; +use serde::Deserialize; +use serde::Serialize; +use std::borrow::Cow; +use std::env; +use std::fmt::Debug; +use std::pin::Pin; +use std::task::Context; +use std::task::Poll; +use std::thread; +use std::time::Duration; +use std::time::SystemTime; + +type SpanProcessor = BatchSpanProcessor<OtelSharedRuntime>; +type LogProcessor = BatchLogProcessor<OtelSharedRuntime>; + +deno_core::extension!( + deno_otel, + ops = [ + op_otel_log, + op_otel_instrumentation_scope_create_and_enter, + op_otel_instrumentation_scope_enter, + op_otel_instrumentation_scope_enter_builtin, + op_otel_span_start, + op_otel_span_continue, + op_otel_span_attribute, + op_otel_span_attribute2, + op_otel_span_attribute3, + op_otel_span_set_dropped, + op_otel_span_flush, + ], +); + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct OtelConfig { + pub runtime_name: Cow<'static, str>, + pub runtime_version: Cow<'static, str>, + pub console: OtelConsoleConfig, + pub deterministic: bool, +} + +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] +#[repr(u8)] +pub enum OtelConsoleConfig { + Ignore = 0, + Capture = 1, + Replace = 2, +} + +impl Default for OtelConfig { + fn default() -> Self { + Self { + runtime_name: Cow::Borrowed(env!("CARGO_PKG_NAME")), + runtime_version: Cow::Borrowed(env!("CARGO_PKG_VERSION")), + console: OtelConsoleConfig::Capture, + deterministic: false, + } + } +} + +static OTEL_SHARED_RUNTIME_SPAWN_TASK_TX: Lazy< + UnboundedSender<BoxFuture<'static, ()>>, +> = Lazy::new(otel_create_shared_runtime); + +fn otel_create_shared_runtime() -> UnboundedSender<BoxFuture<'static, ()>> { + let (spawn_task_tx, mut spawn_task_rx) = + mpsc::unbounded::<BoxFuture<'static, ()>>(); + + thread::spawn(move || { + let rt = create_basic_runtime(); + rt.block_on(async move { + while let Some(task) = spawn_task_rx.next().await { + tokio::spawn(task); + } + }); + }); + + spawn_task_tx +} + +#[derive(Clone, Copy)] +struct OtelSharedRuntime; + +impl hyper::rt::Executor<BoxFuture<'static, ()>> for OtelSharedRuntime { + fn execute(&self, fut: BoxFuture<'static, ()>) { + (*OTEL_SHARED_RUNTIME_SPAWN_TASK_TX) + .unbounded_send(fut) + .expect("failed to send task to shared OpenTelemetry runtime"); + } +} + +impl opentelemetry_sdk::runtime::Runtime for OtelSharedRuntime { + type Interval = Pin<Box<dyn Stream<Item = ()> + Send + 'static>>; + type Delay = Pin<Box<tokio::time::Sleep>>; + + fn interval(&self, period: Duration) -> Self::Interval { + stream::repeat(()) + .then(move |_| tokio::time::sleep(period)) + .boxed() + } + + fn spawn(&self, future: BoxFuture<'static, ()>) { + (*OTEL_SHARED_RUNTIME_SPAWN_TASK_TX) + .unbounded_send(future) + .expect("failed to send task to shared OpenTelemetry runtime"); + } + + fn delay(&self, duration: Duration) -> Self::Delay { + Box::pin(tokio::time::sleep(duration)) + } +} + +impl opentelemetry_sdk::runtime::RuntimeChannel for OtelSharedRuntime { + type Receiver<T: Debug + Send> = BatchMessageChannelReceiver<T>; + type Sender<T: Debug + Send> = BatchMessageChannelSender<T>; + + fn batch_message_channel<T: Debug + Send>( + &self, + capacity: usize, + ) -> (Self::Sender<T>, Self::Receiver<T>) { + let (batch_tx, batch_rx) = tokio::sync::mpsc::channel::<T>(capacity); + (batch_tx.into(), batch_rx.into()) + } +} + +#[derive(Debug)] +pub struct BatchMessageChannelSender<T: Send> { + sender: tokio::sync::mpsc::Sender<T>, +} + +impl<T: Send> From<tokio::sync::mpsc::Sender<T>> + for BatchMessageChannelSender<T> +{ + fn from(sender: tokio::sync::mpsc::Sender<T>) -> Self { + Self { sender } + } +} + +impl<T: Send> opentelemetry_sdk::runtime::TrySend + for BatchMessageChannelSender<T> +{ + type Message = T; + + fn try_send( + &self, + item: Self::Message, + ) -> Result<(), opentelemetry_sdk::runtime::TrySendError> { + self.sender.try_send(item).map_err(|err| match err { + tokio::sync::mpsc::error::TrySendError::Full(_) => { + opentelemetry_sdk::runtime::TrySendError::ChannelFull + } + tokio::sync::mpsc::error::TrySendError::Closed(_) => { + opentelemetry_sdk::runtime::TrySendError::ChannelClosed + } + }) + } +} + +pub struct BatchMessageChannelReceiver<T> { + receiver: tokio::sync::mpsc::Receiver<T>, +} + +impl<T> From<tokio::sync::mpsc::Receiver<T>> + for BatchMessageChannelReceiver<T> +{ + fn from(receiver: tokio::sync::mpsc::Receiver<T>) -> Self { + Self { receiver } + } +} + +impl<T> Stream for BatchMessageChannelReceiver<T> { + type Item = T; + + fn poll_next( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll<Option<Self::Item>> { + self.receiver.poll_recv(cx) + } +} + +mod hyper_client { + use http_body_util::BodyExt; + use http_body_util::Full; + use hyper::body::Body as HttpBody; + use hyper::body::Frame; + use hyper_util::client::legacy::connect::HttpConnector; + use hyper_util::client::legacy::Client; + use opentelemetry_http::Bytes; + use opentelemetry_http::HttpError; + use opentelemetry_http::Request; + use opentelemetry_http::Response; + use opentelemetry_http::ResponseExt; + use std::fmt::Debug; + use std::pin::Pin; + use std::task::Poll; + use std::task::{self}; + + use super::OtelSharedRuntime; + + // same as opentelemetry_http::HyperClient except it uses OtelSharedRuntime + #[derive(Debug, Clone)] + pub struct HyperClient { + inner: Client<HttpConnector, Body>, + } + + impl HyperClient { + pub fn new() -> Self { + Self { + inner: Client::builder(OtelSharedRuntime).build(HttpConnector::new()), + } + } + } + + #[async_trait::async_trait] + impl opentelemetry_http::HttpClient for HyperClient { + async fn send( + &self, + request: Request<Vec<u8>>, + ) -> Result<Response<Bytes>, HttpError> { + let (parts, body) = request.into_parts(); + let request = Request::from_parts(parts, Body(Full::from(body))); + let mut response = self.inner.request(request).await?; + let headers = std::mem::take(response.headers_mut()); + + let mut http_response = Response::builder() + .status(response.status()) + .body(response.into_body().collect().await?.to_bytes())?; + *http_response.headers_mut() = headers; + + Ok(http_response.error_for_status()?) + } + } + + #[pin_project::pin_project] + pub struct Body(#[pin] Full<Bytes>); + + impl HttpBody for Body { + type Data = Bytes; + type Error = Box<dyn std::error::Error + Send + Sync + 'static>; + + #[inline] + fn poll_frame( + self: Pin<&mut Self>, + cx: &mut task::Context<'_>, + ) -> Poll<Option<Result<Frame<Self::Data>, Self::Error>>> { + self.project().0.poll_frame(cx).map_err(Into::into) + } + + #[inline] + fn is_end_stream(&self) -> bool { + self.0.is_end_stream() + } + + #[inline] + fn size_hint(&self) -> hyper::body::SizeHint { + self.0.size_hint() + } + } +} + +static OTEL_PROCESSORS: OnceCell<(SpanProcessor, LogProcessor)> = + OnceCell::new(); + +static BUILT_IN_INSTRUMENTATION_SCOPE: OnceCell< + opentelemetry::InstrumentationScope, +> = OnceCell::new(); + +pub fn init(config: OtelConfig) -> anyhow::Result<()> { + // Parse the `OTEL_EXPORTER_OTLP_PROTOCOL` variable. The opentelemetry_* + // crates don't do this automatically. + // TODO(piscisaureus): enable GRPC support. + let protocol = match env::var("OTEL_EXPORTER_OTLP_PROTOCOL").as_deref() { + Ok("http/protobuf") => Protocol::HttpBinary, + Ok("http/json") => Protocol::HttpJson, + Ok("") | Err(env::VarError::NotPresent) => { + return Ok(()); + } + Ok(protocol) => { + return Err(anyhow!( + "Env var OTEL_EXPORTER_OTLP_PROTOCOL specifies an unsupported protocol: {}", + protocol + )); + } + Err(err) => { + return Err(anyhow!( + "Failed to read env var OTEL_EXPORTER_OTLP_PROTOCOL: {}", + err + )); + } + }; + + // Define the resource attributes that will be attached to all log records. + // These attributes are sourced as follows (in order of precedence): + // * The `service.name` attribute from the `OTEL_SERVICE_NAME` env var. + // * Additional attributes from the `OTEL_RESOURCE_ATTRIBUTES` env var. + // * Default attribute values defined here. + // TODO(piscisaureus): add more default attributes (e.g. script path). + let mut resource = Resource::default(); + + // Add the runtime name and version to the resource attributes. Also override + // the `telemetry.sdk` attributes to include the Deno runtime. + resource = resource.merge(&Resource::new(vec![ + KeyValue::new(PROCESS_RUNTIME_NAME, config.runtime_name), + KeyValue::new(PROCESS_RUNTIME_VERSION, config.runtime_version.clone()), + KeyValue::new( + TELEMETRY_SDK_LANGUAGE, + format!( + "deno-{}", + resource.get(Key::new(TELEMETRY_SDK_LANGUAGE)).unwrap() + ), + ), + KeyValue::new( + TELEMETRY_SDK_NAME, + format!( + "deno-{}", + resource.get(Key::new(TELEMETRY_SDK_NAME)).unwrap() + ), + ), + KeyValue::new( + TELEMETRY_SDK_VERSION, + format!( + "{}-{}", + config.runtime_version, + resource.get(Key::new(TELEMETRY_SDK_VERSION)).unwrap() + ), + ), + ])); + + // The OTLP endpoint is automatically picked up from the + // `OTEL_EXPORTER_OTLP_ENDPOINT` environment variable. Additional headers can + // be specified using `OTEL_EXPORTER_OTLP_HEADERS`. + + let client = hyper_client::HyperClient::new(); + + let span_exporter = HttpExporterBuilder::default() + .with_http_client(client.clone()) + .with_protocol(protocol) + .build_span_exporter()?; + let mut span_processor = + BatchSpanProcessor::builder(span_exporter, OtelSharedRuntime).build(); + span_processor.set_resource(&resource); + + let log_exporter = HttpExporterBuilder::default() + .with_http_client(client) + .with_protocol(protocol) + .build_log_exporter()?; + let log_processor = + BatchLogProcessor::builder(log_exporter, OtelSharedRuntime).build(); + log_processor.set_resource(&resource); + + OTEL_PROCESSORS + .set((span_processor, log_processor)) + .map_err(|_| anyhow!("failed to init otel"))?; + + let builtin_instrumentation_scope = + opentelemetry::InstrumentationScope::builder("deno") + .with_version(config.runtime_version.clone()) + .build(); + BUILT_IN_INSTRUMENTATION_SCOPE + .set(builtin_instrumentation_scope) + .map_err(|_| anyhow!("failed to init otel"))?; + + Ok(()) +} + +/// This function is called by the runtime whenever it is about to call +/// `process::exit()`, to ensure that all OpenTelemetry logs are properly +/// flushed before the process terminates. +pub fn flush() { + if let Some((span_processor, log_processor)) = OTEL_PROCESSORS.get() { + let _ = span_processor.force_flush(); + let _ = log_processor.force_flush(); + } +} + +pub fn handle_log(record: &log::Record) { + use log::Level; + + let Some((_, log_processor)) = OTEL_PROCESSORS.get() else { + return; + }; + + let mut log_record = LogRecord::default(); + + log_record.set_observed_timestamp(SystemTime::now()); + log_record.set_severity_number(match record.level() { + Level::Error => Severity::Error, + Level::Warn => Severity::Warn, + Level::Info => Severity::Info, + Level::Debug => Severity::Debug, + Level::Trace => Severity::Trace, + }); + log_record.set_severity_text(record.level().as_str()); + log_record.set_body(record.args().to_string().into()); + log_record.set_target(record.metadata().target().to_string()); + + struct Visitor<'s>(&'s mut LogRecord); + + impl<'s, 'kvs> log::kv::VisitSource<'kvs> for Visitor<'s> { + fn visit_pair( + &mut self, + key: log::kv::Key<'kvs>, + value: log::kv::Value<'kvs>, + ) -> Result<(), log::kv::Error> { + #[allow(clippy::manual_map)] + let value = if let Some(v) = value.to_bool() { + Some(AnyValue::Boolean(v)) + } else if let Some(v) = value.to_borrowed_str() { + Some(AnyValue::String(v.to_owned().into())) + } else if let Some(v) = value.to_f64() { + Some(AnyValue::Double(v)) + } else if let Some(v) = value.to_i64() { + Some(AnyValue::Int(v)) + } else { + None + }; + + if let Some(value) = value { + let key = Key::from(key.as_str().to_owned()); + self.0.add_attribute(key, value); + } + + Ok(()) + } + } + + let _ = record.key_values().visit(&mut Visitor(&mut log_record)); + + log_processor.emit( + &mut log_record, + BUILT_IN_INSTRUMENTATION_SCOPE.get().unwrap(), + ); +} + +fn parse_trace_id( + scope: &mut v8::HandleScope<'_>, + trace_id: v8::Local<'_, v8::Value>, +) -> TraceId { + if let Ok(string) = trace_id.try_cast() { + let value_view = v8::ValueView::new(scope, string); + match value_view.data() { + v8::ValueViewData::OneByte(bytes) => { + TraceId::from_hex(&String::from_utf8_lossy(bytes)) + .unwrap_or(TraceId::INVALID) + } + + _ => TraceId::INVALID, + } + } else if let Ok(uint8array) = trace_id.try_cast::<v8::Uint8Array>() { + let data = uint8array.data(); + let byte_length = uint8array.byte_length(); + if byte_length != 16 { + return TraceId::INVALID; + } + // SAFETY: We have ensured that the byte length is 16, so it is safe to + // cast the data to an array of 16 bytes. + let bytes = unsafe { &*(data as *const u8 as *const [u8; 16]) }; + TraceId::from_bytes(*bytes) + } else { + TraceId::INVALID + } +} + +fn parse_span_id( + scope: &mut v8::HandleScope<'_>, + span_id: v8::Local<'_, v8::Value>, +) -> SpanId { + if let Ok(string) = span_id.try_cast() { + let value_view = v8::ValueView::new(scope, string); + match value_view.data() { + v8::ValueViewData::OneByte(bytes) => { + SpanId::from_hex(&String::from_utf8_lossy(bytes)) + .unwrap_or(SpanId::INVALID) + } + _ => SpanId::INVALID, + } + } else if let Ok(uint8array) = span_id.try_cast::<v8::Uint8Array>() { + let data = uint8array.data(); + let byte_length = uint8array.byte_length(); + if byte_length != 8 { + return SpanId::INVALID; + } + // SAFETY: We have ensured that the byte length is 8, so it is safe to + // cast the data to an array of 8 bytes. + let bytes = unsafe { &*(data as *const u8 as *const [u8; 8]) }; + SpanId::from_bytes(*bytes) + } else { + SpanId::INVALID + } +} + +macro_rules! attr { + ($scope:ident, $attributes:expr $(=> $dropped_attributes_count:expr)?, $name:expr, $value:expr) => { + let name = if let Ok(name) = $name.try_cast() { + let view = v8::ValueView::new($scope, name); + match view.data() { + v8::ValueViewData::OneByte(bytes) => { + Some(String::from_utf8_lossy(bytes).into_owned()) + } + v8::ValueViewData::TwoByte(bytes) => { + Some(String::from_utf16_lossy(bytes)) + } + } + } else { + None + }; + let value = if let Ok(string) = $value.try_cast::<v8::String>() { + Some(Value::String(StringValue::from({ + let x = v8::ValueView::new($scope, string); + match x.data() { + v8::ValueViewData::OneByte(bytes) => { + String::from_utf8_lossy(bytes).into_owned() + } + v8::ValueViewData::TwoByte(bytes) => String::from_utf16_lossy(bytes), + } + }))) + } else if let Ok(number) = $value.try_cast::<v8::Number>() { + Some(Value::F64(number.value())) + } else if let Ok(boolean) = $value.try_cast::<v8::Boolean>() { + Some(Value::Bool(boolean.is_true())) + } else if let Ok(bigint) = $value.try_cast::<v8::BigInt>() { + let (i64_value, _lossless) = bigint.i64_value(); + Some(Value::I64(i64_value)) + } else { + None + }; + if let (Some(name), Some(value)) = (name, value) { + $attributes.push(KeyValue::new(name, value)); + } + $( + else { + $dropped_attributes_count += 1; + } + )? + }; +} + +#[derive(Debug, Clone)] +struct InstrumentationScope(opentelemetry::InstrumentationScope); + +impl deno_core::GarbageCollected for InstrumentationScope {} + +#[op2] +#[cppgc] +fn op_otel_instrumentation_scope_create_and_enter( + state: &mut OpState, + #[string] name: String, + #[string] version: Option<String>, + #[string] schema_url: Option<String>, +) -> InstrumentationScope { + let mut builder = opentelemetry::InstrumentationScope::builder(name); + if let Some(version) = version { + builder = builder.with_version(version); + } + if let Some(schema_url) = schema_url { + builder = builder.with_schema_url(schema_url); + } + let scope = InstrumentationScope(builder.build()); + state.put(scope.clone()); + scope +} + +#[op2(fast)] +fn op_otel_instrumentation_scope_enter( + state: &mut OpState, + #[cppgc] scope: &InstrumentationScope, +) { + state.put(scope.clone()); +} + +#[op2(fast)] +fn op_otel_instrumentation_scope_enter_builtin(state: &mut OpState) { + state.put(InstrumentationScope( + BUILT_IN_INSTRUMENTATION_SCOPE.get().unwrap().clone(), + )); +} + +#[op2(fast)] +fn op_otel_log( + scope: &mut v8::HandleScope<'_>, + #[string] message: String, + #[smi] level: i32, + trace_id: v8::Local<'_, v8::Value>, + span_id: v8::Local<'_, v8::Value>, + #[smi] trace_flags: u8, +) { + let Some((_, log_processor)) = OTEL_PROCESSORS.get() else { + return; + }; + + // Convert the integer log level that ext/console uses to the corresponding + // OpenTelemetry log severity. + let severity = match level { + ..=0 => Severity::Debug, + 1 => Severity::Info, + 2 => Severity::Warn, + 3.. => Severity::Error, + }; + + let trace_id = parse_trace_id(scope, trace_id); + let span_id = parse_span_id(scope, span_id); + + let mut log_record = LogRecord::default(); + + log_record.set_observed_timestamp(SystemTime::now()); + log_record.set_body(message.into()); + log_record.set_severity_number(severity); + log_record.set_severity_text(severity.name()); + if trace_id != TraceId::INVALID && span_id != SpanId::INVALID { + log_record.set_trace_context( + trace_id, + span_id, + Some(TraceFlags::new(trace_flags)), + ); + } + + log_processor.emit( + &mut log_record, + BUILT_IN_INSTRUMENTATION_SCOPE.get().unwrap(), + ); +} + +struct TemporarySpan(SpanData); + +#[allow(clippy::too_many_arguments)] +#[op2(fast)] +fn op_otel_span_start<'s>( + scope: &mut v8::HandleScope<'s>, + state: &mut OpState, + trace_id: v8::Local<'s, v8::Value>, + span_id: v8::Local<'s, v8::Value>, + parent_span_id: v8::Local<'s, v8::Value>, + #[smi] span_kind: u8, + name: v8::Local<'s, v8::Value>, + start_time: f64, + end_time: f64, +) -> Result<(), anyhow::Error> { + if let Some(temporary_span) = state.try_take::<TemporarySpan>() { + let Some((span_processor, _)) = OTEL_PROCESSORS.get() else { + return Ok(()); + }; + span_processor.on_end(temporary_span.0); + }; + + let Some(InstrumentationScope(instrumentation_scope)) = + state.try_borrow::<InstrumentationScope>() + else { + return Err(anyhow!("instrumentation scope not available")); + }; + + let trace_id = parse_trace_id(scope, trace_id); + if trace_id == TraceId::INVALID { + return Err(anyhow!("invalid trace_id")); + } + + let span_id = parse_span_id(scope, span_id); + if span_id == SpanId::INVALID { + return Err(anyhow!("invalid span_id")); + } + + let parent_span_id = parse_span_id(scope, parent_span_id); + + let name = { + let x = v8::ValueView::new(scope, name.try_cast()?); + match x.data() { + v8::ValueViewData::OneByte(bytes) => { + String::from_utf8_lossy(bytes).into_owned() + } + v8::ValueViewData::TwoByte(bytes) => String::from_utf16_lossy(bytes), + } + }; + + let temporary_span = TemporarySpan(SpanData { + span_context: SpanContext::new( + trace_id, + span_id, + TraceFlags::SAMPLED, + false, + Default::default(), + ), + parent_span_id, + span_kind: match span_kind { + 0 => SpanKind::Internal, + 1 => SpanKind::Server, + 2 => SpanKind::Client, + 3 => SpanKind::Producer, + 4 => SpanKind::Consumer, + _ => return Err(anyhow!("invalid span kind")), + }, + name: Cow::Owned(name), + start_time: SystemTime::UNIX_EPOCH + .checked_add(std::time::Duration::from_secs_f64(start_time)) + .ok_or_else(|| anyhow!("invalid start time"))?, + end_time: SystemTime::UNIX_EPOCH + .checked_add(std::time::Duration::from_secs_f64(end_time)) + .ok_or_else(|| anyhow!("invalid start time"))?, + attributes: Vec::new(), + dropped_attributes_count: 0, + events: Default::default(), + links: Default::default(), + status: SpanStatus::Unset, + instrumentation_scope: instrumentation_scope.clone(), + }); + state.put(temporary_span); + + Ok(()) +} + +#[op2(fast)] +fn op_otel_span_continue( + state: &mut OpState, + #[smi] status: u8, + #[string] error_description: Cow<'_, str>, +) { + if let Some(temporary_span) = state.try_borrow_mut::<TemporarySpan>() { + temporary_span.0.status = match status { + 0 => SpanStatus::Unset, + 1 => SpanStatus::Ok, + 2 => SpanStatus::Error { + description: Cow::Owned(error_description.into_owned()), + }, + _ => return, + }; + } +} + +#[op2(fast)] +fn op_otel_span_attribute<'s>( + scope: &mut v8::HandleScope<'s>, + state: &mut OpState, + #[smi] capacity: u32, + key: v8::Local<'s, v8::Value>, + value: v8::Local<'s, v8::Value>, +) { + if let Some(temporary_span) = state.try_borrow_mut::<TemporarySpan>() { + temporary_span.0.attributes.reserve_exact( + (capacity as usize) - temporary_span.0.attributes.capacity(), + ); + attr!(scope, temporary_span.0.attributes => temporary_span.0.dropped_attributes_count, key, value); + } +} + +#[op2(fast)] +fn op_otel_span_attribute2<'s>( + scope: &mut v8::HandleScope<'s>, + state: &mut OpState, + #[smi] capacity: u32, + key1: v8::Local<'s, v8::Value>, + value1: v8::Local<'s, v8::Value>, + key2: v8::Local<'s, v8::Value>, + value2: v8::Local<'s, v8::Value>, +) { + if let Some(temporary_span) = state.try_borrow_mut::<TemporarySpan>() { + temporary_span.0.attributes.reserve_exact( + (capacity as usize) - temporary_span.0.attributes.capacity(), + ); + attr!(scope, temporary_span.0.attributes => temporary_span.0.dropped_attributes_count, key1, value1); + attr!(scope, temporary_span.0.attributes => temporary_span.0.dropped_attributes_count, key2, value2); + } +} + +#[allow(clippy::too_many_arguments)] +#[op2(fast)] +fn op_otel_span_attribute3<'s>( + scope: &mut v8::HandleScope<'s>, + state: &mut OpState, + #[smi] capacity: u32, + key1: v8::Local<'s, v8::Value>, + value1: v8::Local<'s, v8::Value>, + key2: v8::Local<'s, v8::Value>, + value2: v8::Local<'s, v8::Value>, + key3: v8::Local<'s, v8::Value>, + value3: v8::Local<'s, v8::Value>, +) { + if let Some(temporary_span) = state.try_borrow_mut::<TemporarySpan>() { + temporary_span.0.attributes.reserve_exact( + (capacity as usize) - temporary_span.0.attributes.capacity(), + ); + attr!(scope, temporary_span.0.attributes => temporary_span.0.dropped_attributes_count, key1, value1); + attr!(scope, temporary_span.0.attributes => temporary_span.0.dropped_attributes_count, key2, value2); + attr!(scope, temporary_span.0.attributes => temporary_span.0.dropped_attributes_count, key3, value3); + } +} + +#[op2(fast)] +fn op_otel_span_set_dropped( + state: &mut OpState, + #[smi] dropped_attributes_count: u32, + #[smi] dropped_links_count: u32, + #[smi] dropped_events_count: u32, +) { + if let Some(temporary_span) = state.try_borrow_mut::<TemporarySpan>() { + temporary_span.0.dropped_attributes_count = dropped_attributes_count; + temporary_span.0.links.dropped_count = dropped_links_count; + temporary_span.0.events.dropped_count = dropped_events_count; + } +} + +#[op2(fast)] +fn op_otel_span_flush(state: &mut OpState) { + let Some(temporary_span) = state.try_take::<TemporarySpan>() else { + return; + }; + + let Some((span_processor, _)) = OTEL_PROCESSORS.get() else { + return; + }; + + span_processor.on_end(temporary_span.0); +} diff --git a/runtime/ops/permissions.rs b/runtime/ops/permissions.rs index 1dbc85259..9ad963f3b 100644 --- a/runtime/ops/permissions.rs +++ b/runtime/ops/permissions.rs @@ -2,8 +2,6 @@ use ::deno_permissions::PermissionState; use ::deno_permissions::PermissionsContainer; -use deno_core::error::custom_error; -use deno_core::error::AnyError; use deno_core::op2; use deno_core::OpState; use serde::Deserialize; @@ -47,12 +45,26 @@ impl From<PermissionState> for PermissionStatus { } } +#[derive(Debug, thiserror::Error)] +pub enum PermissionError { + #[error("No such permission name: {0}")] + InvalidPermissionName(String), + #[error("{0}")] + PathResolve(#[from] ::deno_permissions::PathResolveError), + #[error("{0}")] + NetDescriptorParse(#[from] ::deno_permissions::NetDescriptorParseError), + #[error("{0}")] + SysDescriptorParse(#[from] ::deno_permissions::SysDescriptorParseError), + #[error("{0}")] + RunDescriptorParse(#[from] ::deno_permissions::RunDescriptorParseError), +} + #[op2] #[serde] pub fn op_query_permission( state: &mut OpState, #[serde] args: PermissionArgs, -) -> Result<PermissionStatus, AnyError> { +) -> Result<PermissionStatus, PermissionError> { let permissions = state.borrow::<PermissionsContainer>(); let perm = match args.name.as_ref() { "read" => permissions.query_read(args.path.as_deref())?, @@ -62,12 +74,7 @@ pub fn op_query_permission( "sys" => permissions.query_sys(args.kind.as_deref())?, "run" => permissions.query_run(args.command.as_deref())?, "ffi" => permissions.query_ffi(args.path.as_deref())?, - n => { - return Err(custom_error( - "ReferenceError", - format!("No such permission name: {n}"), - )) - } + _ => return Err(PermissionError::InvalidPermissionName(args.name)), }; Ok(PermissionStatus::from(perm)) } @@ -77,7 +84,7 @@ pub fn op_query_permission( pub fn op_revoke_permission( state: &mut OpState, #[serde] args: PermissionArgs, -) -> Result<PermissionStatus, AnyError> { +) -> Result<PermissionStatus, PermissionError> { let permissions = state.borrow::<PermissionsContainer>(); let perm = match args.name.as_ref() { "read" => permissions.revoke_read(args.path.as_deref())?, @@ -87,12 +94,7 @@ pub fn op_revoke_permission( "sys" => permissions.revoke_sys(args.kind.as_deref())?, "run" => permissions.revoke_run(args.command.as_deref())?, "ffi" => permissions.revoke_ffi(args.path.as_deref())?, - n => { - return Err(custom_error( - "ReferenceError", - format!("No such permission name: {n}"), - )) - } + _ => return Err(PermissionError::InvalidPermissionName(args.name)), }; Ok(PermissionStatus::from(perm)) } @@ -102,7 +104,7 @@ pub fn op_revoke_permission( pub fn op_request_permission( state: &mut OpState, #[serde] args: PermissionArgs, -) -> Result<PermissionStatus, AnyError> { +) -> Result<PermissionStatus, PermissionError> { let permissions = state.borrow::<PermissionsContainer>(); let perm = match args.name.as_ref() { "read" => permissions.request_read(args.path.as_deref())?, @@ -112,12 +114,7 @@ pub fn op_request_permission( "sys" => permissions.request_sys(args.kind.as_deref())?, "run" => permissions.request_run(args.command.as_deref())?, "ffi" => permissions.request_ffi(args.path.as_deref())?, - n => { - return Err(custom_error( - "ReferenceError", - format!("No such permission name: {n}"), - )) - } + _ => return Err(PermissionError::InvalidPermissionName(args.name)), }; Ok(PermissionStatus::from(perm)) } diff --git a/runtime/ops/process.rs b/runtime/ops/process.rs index f6555e932..ee2f660dc 100644 --- a/runtime/ops/process.rs +++ b/runtime/ops/process.rs @@ -1,8 +1,5 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -use deno_core::anyhow::Context; -use deno_core::error::type_error; -use deno_core::error::AnyError; use deno_core::op2; use deno_core::serde_json; use deno_core::AsyncMutFuture; @@ -35,6 +32,7 @@ use tokio::process::Command; #[cfg(windows)] use std::os::windows::process::CommandExt; +use crate::ops::signal::SignalError; #[cfg(unix)] use std::os::unix::prelude::ExitStatusExt; #[cfg(unix)] @@ -105,11 +103,12 @@ impl StdioOrRid { pub fn as_stdio( &self, state: &mut OpState, - ) -> Result<std::process::Stdio, AnyError> { + ) -> Result<std::process::Stdio, ProcessError> { match &self { StdioOrRid::Stdio(val) => Ok(val.as_stdio()), StdioOrRid::Rid(rid) => { FileResource::with_file(state, *rid, |file| Ok(file.as_stdio()?)) + .map_err(ProcessError::Resource) } } } @@ -191,6 +190,41 @@ pub struct SpawnArgs { needs_npm_process_state: bool, } +#[derive(Debug, thiserror::Error)] +pub enum ProcessError { + #[error("Failed to spawn '{command}': {error}")] + SpawnFailed { + command: String, + #[source] + error: Box<ProcessError>, + }, + #[error("{0}")] + Io(#[from] std::io::Error), + #[cfg(unix)] + #[error(transparent)] + Nix(nix::Error), + #[error("failed resolving cwd: {0}")] + FailedResolvingCwd(#[source] std::io::Error), + #[error(transparent)] + Permission(#[from] deno_permissions::PermissionCheckError), + #[error(transparent)] + RunPermission(#[from] CheckRunPermissionError), + #[error(transparent)] + Resource(deno_core::error::AnyError), + #[error(transparent)] + BorrowMut(std::cell::BorrowMutError), + #[error(transparent)] + Which(which::Error), + #[error("Child process has already terminated.")] + ChildProcessAlreadyTerminated, + #[error("Invalid pid")] + InvalidPid, + #[error(transparent)] + Signal(#[from] SignalError), + #[error("Missing cmd")] + MissingCmd, // only for Deno.run +} + #[derive(Deserialize)] #[serde(rename_all = "camelCase")] pub struct ChildStdio { @@ -208,7 +242,7 @@ pub struct ChildStatus { } impl TryFrom<ExitStatus> for ChildStatus { - type Error = AnyError; + type Error = SignalError; fn try_from(status: ExitStatus) -> Result<Self, Self::Error> { let code = status.code(); @@ -259,7 +293,7 @@ type CreateCommand = ( pub fn npm_process_state_tempfile( contents: &[u8], -) -> Result<deno_io::RawIoHandle, AnyError> { +) -> Result<deno_io::RawIoHandle, std::io::Error> { let mut temp_file = tempfile::tempfile()?; temp_file.write_all(contents)?; let handle = temp_file.into_raw_io_handle(); @@ -301,7 +335,7 @@ fn create_command( state: &mut OpState, mut args: SpawnArgs, api_name: &str, -) -> Result<CreateCommand, AnyError> { +) -> Result<CreateCommand, ProcessError> { let maybe_npm_process_state = if args.needs_npm_process_state { let provider = state.borrow::<NpmProcessStateProviderRc>(); let process_state = provider.get_npm_process_state(); @@ -505,7 +539,7 @@ fn spawn_child( ipc_pipe_rid: Option<ResourceId>, extra_pipe_rids: Vec<Option<ResourceId>>, detached: bool, -) -> Result<Child, AnyError> { +) -> Result<Child, ProcessError> { let mut command = tokio::process::Command::from(command); // TODO(@crowlkats): allow detaching processes. // currently deno will orphan a process when exiting with an error or Deno.exit() @@ -554,10 +588,10 @@ fn spawn_child( } } - return Err(AnyError::from(err).context(format!( - "Failed to spawn '{}'", - command.get_program().to_string_lossy() - ))); + return Err(ProcessError::SpawnFailed { + command: command.get_program().to_string_lossy().to_string(), + error: Box::new(err.into()), + }); } }; @@ -600,11 +634,19 @@ fn compute_run_cmd_and_check_permissions( arg_clear_env: bool, state: &mut OpState, api_name: &str, -) -> Result<(PathBuf, RunEnv), AnyError> { - let run_env = compute_run_env(arg_cwd, arg_envs, arg_clear_env) - .with_context(|| format!("Failed to spawn '{}'", arg_cmd))?; - let cmd = resolve_cmd(arg_cmd, &run_env) - .with_context(|| format!("Failed to spawn '{}'", arg_cmd))?; +) -> Result<(PathBuf, RunEnv), ProcessError> { + let run_env = + compute_run_env(arg_cwd, arg_envs, arg_clear_env).map_err(|e| { + ProcessError::SpawnFailed { + command: arg_cmd.to_string(), + error: Box::new(e), + } + })?; + let cmd = + resolve_cmd(arg_cmd, &run_env).map_err(|e| ProcessError::SpawnFailed { + command: arg_cmd.to_string(), + error: Box::new(e), + })?; check_run_permission( state, &RunQueryDescriptor::Path { @@ -631,9 +673,10 @@ fn compute_run_env( arg_cwd: Option<&str>, arg_envs: &[(String, String)], arg_clear_env: bool, -) -> Result<RunEnv, AnyError> { +) -> Result<RunEnv, ProcessError> { #[allow(clippy::disallowed_methods)] - let cwd = std::env::current_dir().context("failed resolving cwd")?; + let cwd = + std::env::current_dir().map_err(ProcessError::FailedResolvingCwd)?; let cwd = arg_cwd .map(|cwd_arg| resolve_path(cwd_arg, &cwd)) .unwrap_or(cwd); @@ -670,7 +713,7 @@ fn compute_run_env( Ok(RunEnv { envs, cwd }) } -fn resolve_cmd(cmd: &str, env: &RunEnv) -> Result<PathBuf, AnyError> { +fn resolve_cmd(cmd: &str, env: &RunEnv) -> Result<PathBuf, ProcessError> { let is_path = cmd.contains('/'); #[cfg(windows)] let is_path = is_path || cmd.contains('\\') || Path::new(&cmd).is_absolute(); @@ -683,7 +726,7 @@ fn resolve_cmd(cmd: &str, env: &RunEnv) -> Result<PathBuf, AnyError> { Err(which::Error::CannotFindBinaryPath) => { Err(std::io::Error::from(std::io::ErrorKind::NotFound).into()) } - Err(err) => Err(err.into()), + Err(err) => Err(ProcessError::Which(err)), } } } @@ -692,12 +735,20 @@ fn resolve_path(path: &str, cwd: &Path) -> PathBuf { deno_path_util::normalize_path(cwd.join(path)) } +#[derive(Debug, thiserror::Error)] +pub enum CheckRunPermissionError { + #[error(transparent)] + Permission(#[from] deno_permissions::PermissionCheckError), + #[error("{0}")] + Other(deno_core::error::AnyError), +} + fn check_run_permission( state: &mut OpState, cmd: &RunQueryDescriptor, run_env: &RunEnv, api_name: &str, -) -> Result<(), AnyError> { +) -> Result<(), CheckRunPermissionError> { let permissions = state.borrow_mut::<PermissionsContainer>(); if !permissions.query_run_all(api_name) { // error the same on all platforms @@ -705,13 +756,16 @@ fn check_run_permission( if !env_var_names.is_empty() { // we don't allow users to launch subprocesses with any LD_ or DYLD_* // env vars set because this allows executing code (ex. LD_PRELOAD) - return Err(deno_core::error::custom_error( - "NotCapable", - format!( - "Requires --allow-all permissions to spawn subprocess with {} environment variable{}.", - env_var_names.join(", "), - if env_var_names.len() != 1 { "s" } else { "" } - ) + return Err(CheckRunPermissionError::Other( + deno_core::error::custom_error( + "NotCapable", + format!( + "Requires --allow-run permissions to spawn subprocess with {0} environment variable{1}. Alternatively, spawn with {2} environment variable{1} unset.", + env_var_names.join(", "), + if env_var_names.len() != 1 { "s" } else { "" }, + if env_var_names.len() != 1 { "these" } else { "the" } + ), + ), )); } permissions.check_run(cmd, api_name)?; @@ -754,7 +808,7 @@ fn op_spawn_child( state: &mut OpState, #[serde] args: SpawnArgs, #[string] api_name: String, -) -> Result<Child, AnyError> { +) -> Result<Child, ProcessError> { let detached = args.detached; let (command, pipe_rid, extra_pipe_rids, handles_to_close) = create_command(state, args, &api_name)?; @@ -771,16 +825,23 @@ fn op_spawn_child( async fn op_spawn_wait( state: Rc<RefCell<OpState>>, #[smi] rid: ResourceId, -) -> Result<ChildStatus, AnyError> { +) -> Result<ChildStatus, ProcessError> { let resource = state .borrow_mut() .resource_table - .get::<ChildResource>(rid)?; - let result = resource.0.try_borrow_mut()?.wait().await?.try_into(); + .get::<ChildResource>(rid) + .map_err(ProcessError::Resource)?; + let result = resource + .0 + .try_borrow_mut() + .map_err(ProcessError::BorrowMut)? + .wait() + .await? + .try_into()?; if let Ok(resource) = state.borrow_mut().resource_table.take_any(rid) { resource.close(); } - result + Ok(result) } #[op2] @@ -788,16 +849,14 @@ async fn op_spawn_wait( fn op_spawn_sync( state: &mut OpState, #[serde] args: SpawnArgs, -) -> Result<SpawnOutput, AnyError> { +) -> Result<SpawnOutput, ProcessError> { let stdout = matches!(args.stdio.stdout, StdioOrRid::Stdio(Stdio::Piped)); let stderr = matches!(args.stdio.stderr, StdioOrRid::Stdio(Stdio::Piped)); let (mut command, _, _, _) = create_command(state, args, "Deno.Command().outputSync()")?; - let output = command.output().with_context(|| { - format!( - "Failed to spawn '{}'", - command.get_program().to_string_lossy() - ) + let output = command.output().map_err(|e| ProcessError::SpawnFailed { + command: command.get_program().to_string_lossy().to_string(), + error: Box::new(e.into()), })?; Ok(SpawnOutput { @@ -820,17 +879,15 @@ fn op_spawn_kill( state: &mut OpState, #[smi] rid: ResourceId, #[string] signal: String, -) -> Result<(), AnyError> { +) -> Result<(), ProcessError> { if let Ok(child_resource) = state.resource_table.get::<ChildResource>(rid) { deprecated::kill(child_resource.1 as i32, &signal)?; return Ok(()); } - Err(type_error("Child process has already terminated.")) + Err(ProcessError::ChildProcessAlreadyTerminated) } mod deprecated { - use deno_core::anyhow; - use super::*; #[derive(Deserialize)] @@ -876,9 +933,9 @@ mod deprecated { pub fn op_run( state: &mut OpState, #[serde] run_args: RunArgs, - ) -> Result<RunInfo, AnyError> { + ) -> Result<RunInfo, ProcessError> { let args = run_args.cmd; - let cmd = args.first().ok_or_else(|| anyhow::anyhow!("Missing cmd"))?; + let cmd = args.first().ok_or(ProcessError::MissingCmd)?; let (cmd, run_env) = compute_run_cmd_and_check_permissions( cmd, run_args.cwd.as_deref(), @@ -990,11 +1047,12 @@ mod deprecated { pub async fn op_run_status( state: Rc<RefCell<OpState>>, #[smi] rid: ResourceId, - ) -> Result<ProcessStatus, AnyError> { + ) -> Result<ProcessStatus, ProcessError> { let resource = state .borrow_mut() .resource_table - .get::<ChildResource>(rid)?; + .get::<ChildResource>(rid) + .map_err(ProcessError::Resource)?; let mut child = resource.borrow_mut().await; let run_status = child.wait().await?; let code = run_status.code(); @@ -1017,17 +1075,17 @@ mod deprecated { } #[cfg(unix)] - pub fn kill(pid: i32, signal: &str) -> Result<(), AnyError> { + pub fn kill(pid: i32, signal: &str) -> Result<(), ProcessError> { let signo = super::super::signal::signal_str_to_int(signal)?; use nix::sys::signal::kill as unix_kill; use nix::sys::signal::Signal; use nix::unistd::Pid; - let sig = Signal::try_from(signo)?; - unix_kill(Pid::from_raw(pid), Option::Some(sig)).map_err(AnyError::from) + let sig = Signal::try_from(signo).map_err(ProcessError::Nix)?; + unix_kill(Pid::from_raw(pid), Some(sig)).map_err(ProcessError::Nix) } #[cfg(not(unix))] - pub fn kill(pid: i32, signal: &str) -> Result<(), AnyError> { + pub fn kill(pid: i32, signal: &str) -> Result<(), ProcessError> { use std::io::Error; use std::io::ErrorKind::NotFound; use winapi::shared::minwindef::DWORD; @@ -1041,9 +1099,9 @@ mod deprecated { use winapi::um::winnt::PROCESS_TERMINATE; if !matches!(signal, "SIGKILL" | "SIGTERM") { - Err(type_error(format!("Invalid signal: {signal}"))) + Err(SignalError::InvalidSignalStr(signal.to_string()).into()) } else if pid <= 0 { - Err(type_error("Invalid pid")) + Err(ProcessError::InvalidPid) } else { let handle = // SAFETY: winapi call @@ -1077,11 +1135,10 @@ mod deprecated { #[smi] pid: i32, #[string] signal: String, #[string] api_name: String, - ) -> Result<(), AnyError> { + ) -> Result<(), ProcessError> { state .borrow_mut::<PermissionsContainer>() .check_run_all(&api_name)?; - kill(pid, &signal)?; - Ok(()) + kill(pid, &signal) } } diff --git a/runtime/ops/runtime.rs b/runtime/ops/runtime.rs index 419274ebd..8d54783fc 100644 --- a/runtime/ops/runtime.rs +++ b/runtime/ops/runtime.rs @@ -1,6 +1,5 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -use deno_core::error::AnyError; use deno_core::op2; use deno_core::ModuleSpecifier; use deno_core::OpState; @@ -16,10 +15,9 @@ deno_core::extension!( #[op2] #[string] -fn op_main_module(state: &mut OpState) -> Result<String, AnyError> { +fn op_main_module(state: &mut OpState) -> String { let main_url = state.borrow::<ModuleSpecifier>(); - let main_path = main_url.to_string(); - Ok(main_path) + main_url.to_string() } /// This is an op instead of being done at initialization time because diff --git a/runtime/ops/signal.rs b/runtime/ops/signal.rs index ebc6db6d1..e1e4ab68b 100644 --- a/runtime/ops/signal.rs +++ b/runtime/ops/signal.rs @@ -1,6 +1,4 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -use deno_core::error::type_error; -use deno_core::error::AnyError; use deno_core::op2; use deno_core::AsyncRefCell; use deno_core::CancelFuture; @@ -46,6 +44,42 @@ deno_core::extension!( } ); +#[derive(Debug, thiserror::Error)] +pub enum SignalError { + #[cfg(any( + target_os = "android", + target_os = "linux", + target_os = "openbsd", + target_os = "openbsd", + target_os = "macos", + target_os = "solaris", + target_os = "illumos" + ))] + #[error("Invalid signal: {0}")] + InvalidSignalStr(String), + #[cfg(any( + target_os = "android", + target_os = "linux", + target_os = "openbsd", + target_os = "openbsd", + target_os = "macos", + target_os = "solaris", + target_os = "illumos" + ))] + #[error("Invalid signal: {0}")] + InvalidSignalInt(libc::c_int), + #[cfg(target_os = "windows")] + #[error("Windows only supports ctrl-c (SIGINT) and ctrl-break (SIGBREAK), but got {0}")] + InvalidSignalStr(String), + #[cfg(target_os = "windows")] + #[error("Windows only supports ctrl-c (SIGINT) and ctrl-break (SIGBREAK), but got {0}")] + InvalidSignalInt(libc::c_int), + #[error("Binding to signal '{0}' is not allowed")] + SignalNotAllowed(String), + #[error("{0}")] + Io(#[from] std::io::Error), +} + #[cfg(unix)] #[derive(Default)] struct SignalState { @@ -147,438 +181,217 @@ impl Resource for SignalStreamResource { } } -#[cfg(target_os = "freebsd")] -pub fn signal_str_to_int(s: &str) -> Result<libc::c_int, AnyError> { - match s { - "SIGHUP" => Ok(1), - "SIGINT" => Ok(2), - "SIGQUIT" => Ok(3), - "SIGILL" => Ok(4), - "SIGTRAP" => Ok(5), - "SIGIOT" => Ok(6), - "SIGABRT" => Ok(6), - "SIGEMT" => Ok(7), - "SIGFPE" => Ok(8), - "SIGKILL" => Ok(9), - "SIGBUS" => Ok(10), - "SIGSEGV" => Ok(11), - "SIGSYS" => Ok(12), - "SIGPIPE" => Ok(13), - "SIGALRM" => Ok(14), - "SIGTERM" => Ok(15), - "SIGURG" => Ok(16), - "SIGSTOP" => Ok(17), - "SIGTSTP" => Ok(18), - "SIGCONT" => Ok(19), - "SIGCHLD" => Ok(20), - "SIGTTIN" => Ok(21), - "SIGTTOU" => Ok(22), - "SIGIO" => Ok(23), - "SIGXCPU" => Ok(24), - "SIGXFSZ" => Ok(25), - "SIGVTALRM" => Ok(26), - "SIGPROF" => Ok(27), - "SIGWINCH" => Ok(28), - "SIGINFO" => Ok(29), - "SIGUSR1" => Ok(30), - "SIGUSR2" => Ok(31), - "SIGTHR" => Ok(32), - "SIGLIBRT" => Ok(33), - _ => Err(type_error(format!("Invalid signal : {}", s))), - } +macro_rules! first_literal { + ($head:literal $(, $tail:literal)*) => { + $head + }; } +macro_rules! signal_dict { + ($(($number:literal, $($name:literal)|+)),*) => { + pub fn signal_str_to_int(s: &str) -> Result<libc::c_int, SignalError> { + match s { + $($($name)|* => Ok($number),)* + _ => Err(SignalError::InvalidSignalStr(s.to_string())), + } + } -#[cfg(target_os = "freebsd")] -pub fn signal_int_to_str(s: libc::c_int) -> Result<&'static str, AnyError> { - match s { - 1 => Ok("SIGHUP"), - 2 => Ok("SIGINT"), - 3 => Ok("SIGQUIT"), - 4 => Ok("SIGILL"), - 5 => Ok("SIGTRAP"), - 6 => Ok("SIGABRT"), - 7 => Ok("SIGEMT"), - 8 => Ok("SIGFPE"), - 9 => Ok("SIGKILL"), - 10 => Ok("SIGBUS"), - 11 => Ok("SIGSEGV"), - 12 => Ok("SIGSYS"), - 13 => Ok("SIGPIPE"), - 14 => Ok("SIGALRM"), - 15 => Ok("SIGTERM"), - 16 => Ok("SIGURG"), - 17 => Ok("SIGSTOP"), - 18 => Ok("SIGTSTP"), - 19 => Ok("SIGCONT"), - 20 => Ok("SIGCHLD"), - 21 => Ok("SIGTTIN"), - 22 => Ok("SIGTTOU"), - 23 => Ok("SIGIO"), - 24 => Ok("SIGXCPU"), - 25 => Ok("SIGXFSZ"), - 26 => Ok("SIGVTALRM"), - 27 => Ok("SIGPROF"), - 28 => Ok("SIGWINCH"), - 29 => Ok("SIGINFO"), - 30 => Ok("SIGUSR1"), - 31 => Ok("SIGUSR2"), - 32 => Ok("SIGTHR"), - 33 => Ok("SIGLIBRT"), - _ => Err(type_error(format!("Invalid signal : {}", s))), + pub fn signal_int_to_str(s: libc::c_int) -> Result<&'static str, SignalError> { + match s { + $($number => Ok(first_literal!($($name),+)),)* + _ => Err(SignalError::InvalidSignalInt(s)), + } + } } } -#[cfg(target_os = "openbsd")] -pub fn signal_str_to_int(s: &str) -> Result<libc::c_int, AnyError> { - match s { - "SIGHUP" => Ok(1), - "SIGINT" => Ok(2), - "SIGQUIT" => Ok(3), - "SIGILL" => Ok(4), - "SIGTRAP" => Ok(5), - "SIGIOT" => Ok(6), - "SIGABRT" => Ok(6), - "SIGEMT" => Ok(7), - "SIGFPE" => Ok(8), - "SIGKILL" => Ok(9), - "SIGBUS" => Ok(10), - "SIGSEGV" => Ok(11), - "SIGSYS" => Ok(12), - "SIGPIPE" => Ok(13), - "SIGALRM" => Ok(14), - "SIGTERM" => Ok(15), - "SIGURG" => Ok(16), - "SIGSTOP" => Ok(17), - "SIGTSTP" => Ok(18), - "SIGCONT" => Ok(19), - "SIGCHLD" => Ok(20), - "SIGTTIN" => Ok(21), - "SIGTTOU" => Ok(22), - "SIGIO" => Ok(23), - "SIGXCPU" => Ok(24), - "SIGXFSZ" => Ok(25), - "SIGVTALRM" => Ok(26), - "SIGPROF" => Ok(27), - "SIGWINCH" => Ok(28), - "SIGINFO" => Ok(29), - "SIGUSR1" => Ok(30), - "SIGUSR2" => Ok(31), - "SIGTHR" => Ok(32), - _ => Err(type_error(format!("Invalid signal : {}", s))), - } -} +#[cfg(target_os = "freebsd")] +signal_dict!( + (1, "SIGHUP"), + (2, "SIGINT"), + (3, "SIGQUIT"), + (4, "SIGILL"), + (5, "SIGTRAP"), + (6, "SIGABRT" | "SIGIOT"), + (7, "SIGEMT"), + (8, "SIGFPE"), + (9, "SIGKILL"), + (10, "SIGBUS"), + (11, "SIGSEGV"), + (12, "SIGSYS"), + (13, "SIGPIPE"), + (14, "SIGALRM"), + (15, "SIGTERM"), + (16, "SIGURG"), + (17, "SIGSTOP"), + (18, "SIGTSTP"), + (19, "SIGCONT"), + (20, "SIGCHLD"), + (21, "SIGTTIN"), + (22, "SIGTTOU"), + (23, "SIGIO"), + (24, "SIGXCPU"), + (25, "SIGXFSZ"), + (26, "SIGVTALRM"), + (27, "SIGPROF"), + (28, "SIGWINCH"), + (29, "SIGINFO"), + (30, "SIGUSR1"), + (31, "SIGUSR2"), + (32, "SIGTHR"), + (33, "SIGLIBRT") +); #[cfg(target_os = "openbsd")] -pub fn signal_int_to_str(s: libc::c_int) -> Result<&'static str, AnyError> { - match s { - 1 => Ok("SIGHUP"), - 2 => Ok("SIGINT"), - 3 => Ok("SIGQUIT"), - 4 => Ok("SIGILL"), - 5 => Ok("SIGTRAP"), - 6 => Ok("SIGABRT"), - 7 => Ok("SIGEMT"), - 8 => Ok("SIGFPE"), - 9 => Ok("SIGKILL"), - 10 => Ok("SIGBUS"), - 11 => Ok("SIGSEGV"), - 12 => Ok("SIGSYS"), - 13 => Ok("SIGPIPE"), - 14 => Ok("SIGALRM"), - 15 => Ok("SIGTERM"), - 16 => Ok("SIGURG"), - 17 => Ok("SIGSTOP"), - 18 => Ok("SIGTSTP"), - 19 => Ok("SIGCONT"), - 20 => Ok("SIGCHLD"), - 21 => Ok("SIGTTIN"), - 22 => Ok("SIGTTOU"), - 23 => Ok("SIGIO"), - 24 => Ok("SIGXCPU"), - 25 => Ok("SIGXFSZ"), - 26 => Ok("SIGVTALRM"), - 27 => Ok("SIGPROF"), - 28 => Ok("SIGWINCH"), - 29 => Ok("SIGINFO"), - 30 => Ok("SIGUSR1"), - 31 => Ok("SIGUSR2"), - 32 => Ok("SIGTHR"), - _ => Err(type_error(format!("Invalid signal : {}", s))), - } -} - -#[cfg(any(target_os = "android", target_os = "linux"))] -pub fn signal_str_to_int(s: &str) -> Result<libc::c_int, AnyError> { - match s { - "SIGHUP" => Ok(1), - "SIGINT" => Ok(2), - "SIGQUIT" => Ok(3), - "SIGILL" => Ok(4), - "SIGTRAP" => Ok(5), - "SIGIOT" => Ok(6), - "SIGABRT" => Ok(6), - "SIGBUS" => Ok(7), - "SIGFPE" => Ok(8), - "SIGKILL" => Ok(9), - "SIGUSR1" => Ok(10), - "SIGSEGV" => Ok(11), - "SIGUSR2" => Ok(12), - "SIGPIPE" => Ok(13), - "SIGALRM" => Ok(14), - "SIGTERM" => Ok(15), - "SIGSTKFLT" => Ok(16), - "SIGCHLD" => Ok(17), - "SIGCONT" => Ok(18), - "SIGSTOP" => Ok(19), - "SIGTSTP" => Ok(20), - "SIGTTIN" => Ok(21), - "SIGTTOU" => Ok(22), - "SIGURG" => Ok(23), - "SIGXCPU" => Ok(24), - "SIGXFSZ" => Ok(25), - "SIGVTALRM" => Ok(26), - "SIGPROF" => Ok(27), - "SIGWINCH" => Ok(28), - "SIGIO" | "SIGPOLL" => Ok(29), - "SIGPWR" => Ok(30), - "SIGSYS" | "SIGUNUSED" => Ok(31), - _ => Err(type_error(format!("Invalid signal : {s}"))), - } -} +signal_dict!( + (1, "SIGHUP"), + (2, "SIGINT"), + (3, "SIGQUIT"), + (4, "SIGILL"), + (5, "SIGTRAP"), + (6, "SIGABRT" | "SIGIOT"), + (7, "SIGEMT"), + (8, "SIGKILL"), + (10, "SIGBUS"), + (11, "SIGSEGV"), + (12, "SIGSYS"), + (13, "SIGPIPE"), + (14, "SIGALRM"), + (15, "SIGTERM"), + (16, "SIGURG"), + (17, "SIGSTOP"), + (18, "SIGTSTP"), + (19, "SIGCONT"), + (20, "SIGCHLD"), + (21, "SIGTTIN"), + (22, "SIGTTOU"), + (23, "SIGIO"), + (24, "SIGXCPU"), + (25, "SIGXFSZ"), + (26, "SIGVTALRM"), + (27, "SIGPROF"), + (28, "SIGWINCH"), + (29, "SIGINFO"), + (30, "SIGUSR1"), + (31, "SIGUSR2"), + (32, "SIGTHR") +); #[cfg(any(target_os = "android", target_os = "linux"))] -pub fn signal_int_to_str(s: libc::c_int) -> Result<&'static str, AnyError> { - match s { - 1 => Ok("SIGHUP"), - 2 => Ok("SIGINT"), - 3 => Ok("SIGQUIT"), - 4 => Ok("SIGILL"), - 5 => Ok("SIGTRAP"), - 6 => Ok("SIGABRT"), - 7 => Ok("SIGBUS"), - 8 => Ok("SIGFPE"), - 9 => Ok("SIGKILL"), - 10 => Ok("SIGUSR1"), - 11 => Ok("SIGSEGV"), - 12 => Ok("SIGUSR2"), - 13 => Ok("SIGPIPE"), - 14 => Ok("SIGALRM"), - 15 => Ok("SIGTERM"), - 16 => Ok("SIGSTKFLT"), - 17 => Ok("SIGCHLD"), - 18 => Ok("SIGCONT"), - 19 => Ok("SIGSTOP"), - 20 => Ok("SIGTSTP"), - 21 => Ok("SIGTTIN"), - 22 => Ok("SIGTTOU"), - 23 => Ok("SIGURG"), - 24 => Ok("SIGXCPU"), - 25 => Ok("SIGXFSZ"), - 26 => Ok("SIGVTALRM"), - 27 => Ok("SIGPROF"), - 28 => Ok("SIGWINCH"), - 29 => Ok("SIGIO"), - 30 => Ok("SIGPWR"), - 31 => Ok("SIGSYS"), - _ => Err(type_error(format!("Invalid signal : {s}"))), - } -} - -#[cfg(target_os = "macos")] -pub fn signal_str_to_int(s: &str) -> Result<libc::c_int, AnyError> { - match s { - "SIGHUP" => Ok(1), - "SIGINT" => Ok(2), - "SIGQUIT" => Ok(3), - "SIGILL" => Ok(4), - "SIGTRAP" => Ok(5), - "SIGIOT" => Ok(6), - "SIGABRT" => Ok(6), - "SIGEMT" => Ok(7), - "SIGFPE" => Ok(8), - "SIGKILL" => Ok(9), - "SIGBUS" => Ok(10), - "SIGSEGV" => Ok(11), - "SIGSYS" => Ok(12), - "SIGPIPE" => Ok(13), - "SIGALRM" => Ok(14), - "SIGTERM" => Ok(15), - "SIGURG" => Ok(16), - "SIGSTOP" => Ok(17), - "SIGTSTP" => Ok(18), - "SIGCONT" => Ok(19), - "SIGCHLD" => Ok(20), - "SIGTTIN" => Ok(21), - "SIGTTOU" => Ok(22), - "SIGIO" => Ok(23), - "SIGXCPU" => Ok(24), - "SIGXFSZ" => Ok(25), - "SIGVTALRM" => Ok(26), - "SIGPROF" => Ok(27), - "SIGWINCH" => Ok(28), - "SIGINFO" => Ok(29), - "SIGUSR1" => Ok(30), - "SIGUSR2" => Ok(31), - _ => Err(type_error(format!("Invalid signal: {s}"))), - } -} +signal_dict!( + (1, "SIGHUP"), + (2, "SIGINT"), + (3, "SIGQUIT"), + (4, "SIGILL"), + (5, "SIGTRAP"), + (6, "SIGABRT" | "SIGIOT"), + (7, "SIGBUS"), + (8, "SIGFPE"), + (9, "SIGKILL"), + (10, "SIGUSR1"), + (11, "SIGSEGV"), + (12, "SIGUSR2"), + (13, "SIGPIPE"), + (14, "SIGALRM"), + (15, "SIGTERM"), + (16, "SIGSTKFLT"), + (17, "SIGCHLD"), + (18, "SIGCONT"), + (19, "SIGSTOP"), + (20, "SIGTSTP"), + (21, "SIGTTIN"), + (22, "SIGTTOU"), + (23, "SIGURG"), + (24, "SIGXCPU"), + (25, "SIGXFSZ"), + (26, "SIGVTALRM"), + (27, "SIGPROF"), + (28, "SIGWINCH"), + (29, "SIGIO" | "SIGPOLL"), + (30, "SIGPWR"), + (31, "SIGSYS" | "SIGUNUSED") +); #[cfg(target_os = "macos")] -pub fn signal_int_to_str(s: libc::c_int) -> Result<&'static str, AnyError> { - match s { - 1 => Ok("SIGHUP"), - 2 => Ok("SIGINT"), - 3 => Ok("SIGQUIT"), - 4 => Ok("SIGILL"), - 5 => Ok("SIGTRAP"), - 6 => Ok("SIGABRT"), - 7 => Ok("SIGEMT"), - 8 => Ok("SIGFPE"), - 9 => Ok("SIGKILL"), - 10 => Ok("SIGBUS"), - 11 => Ok("SIGSEGV"), - 12 => Ok("SIGSYS"), - 13 => Ok("SIGPIPE"), - 14 => Ok("SIGALRM"), - 15 => Ok("SIGTERM"), - 16 => Ok("SIGURG"), - 17 => Ok("SIGSTOP"), - 18 => Ok("SIGTSTP"), - 19 => Ok("SIGCONT"), - 20 => Ok("SIGCHLD"), - 21 => Ok("SIGTTIN"), - 22 => Ok("SIGTTOU"), - 23 => Ok("SIGIO"), - 24 => Ok("SIGXCPU"), - 25 => Ok("SIGXFSZ"), - 26 => Ok("SIGVTALRM"), - 27 => Ok("SIGPROF"), - 28 => Ok("SIGWINCH"), - 29 => Ok("SIGINFO"), - 30 => Ok("SIGUSR1"), - 31 => Ok("SIGUSR2"), - _ => Err(type_error(format!("Invalid signal: {s}"))), - } -} - -#[cfg(any(target_os = "solaris", target_os = "illumos"))] -pub fn signal_str_to_int(s: &str) -> Result<libc::c_int, AnyError> { - match s { - "SIGHUP" => Ok(1), - "SIGINT" => Ok(2), - "SIGQUIT" => Ok(3), - "SIGILL" => Ok(4), - "SIGTRAP" => Ok(5), - "SIGIOT" => Ok(6), - "SIGABRT" => Ok(6), - "SIGEMT" => Ok(7), - "SIGFPE" => Ok(8), - "SIGKILL" => Ok(9), - "SIGBUS" => Ok(10), - "SIGSEGV" => Ok(11), - "SIGSYS" => Ok(12), - "SIGPIPE" => Ok(13), - "SIGALRM" => Ok(14), - "SIGTERM" => Ok(15), - "SIGUSR1" => Ok(16), - "SIGUSR2" => Ok(17), - "SIGCLD" => Ok(18), - "SIGCHLD" => Ok(18), - "SIGPWR" => Ok(19), - "SIGWINCH" => Ok(20), - "SIGURG" => Ok(21), - "SIGPOLL" => Ok(22), - "SIGIO" => Ok(22), - "SIGSTOP" => Ok(23), - "SIGTSTP" => Ok(24), - "SIGCONT" => Ok(25), - "SIGTTIN" => Ok(26), - "SIGTTOU" => Ok(27), - "SIGVTALRM" => Ok(28), - "SIGPROF" => Ok(29), - "SIGXCPU" => Ok(30), - "SIGXFSZ" => Ok(31), - "SIGWAITING" => Ok(32), - "SIGLWP" => Ok(33), - "SIGFREEZE" => Ok(34), - "SIGTHAW" => Ok(35), - "SIGCANCEL" => Ok(36), - "SIGLOST" => Ok(37), - "SIGXRES" => Ok(38), - "SIGJVM1" => Ok(39), - "SIGJVM2" => Ok(40), - _ => Err(type_error(format!("Invalid signal : {}", s))), - } -} +signal_dict!( + (1, "SIGHUP"), + (2, "SIGINT"), + (3, "SIGQUIT"), + (4, "SIGILL"), + (5, "SIGTRAP"), + (6, "SIGABRT" | "SIGIOT"), + (7, "SIGEMT"), + (8, "SIGFPE"), + (9, "SIGKILL"), + (10, "SIGBUS"), + (11, "SIGSEGV"), + (12, "SIGSYS"), + (13, "SIGPIPE"), + (14, "SIGALRM"), + (15, "SIGTERM"), + (16, "SIGURG"), + (17, "SIGSTOP"), + (18, "SIGTSTP"), + (19, "SIGCONT"), + (20, "SIGCHLD"), + (21, "SIGTTIN"), + (22, "SIGTTOU"), + (23, "SIGIO"), + (24, "SIGXCPU"), + (25, "SIGXFSZ"), + (26, "SIGVTALRM"), + (27, "SIGPROF"), + (28, "SIGWINCH"), + (29, "SIGINFO"), + (30, "SIGUSR1"), + (31, "SIGUSR2") +); #[cfg(any(target_os = "solaris", target_os = "illumos"))] -pub fn signal_int_to_str(s: libc::c_int) -> Result<&'static str, AnyError> { - match s { - 1 => Ok("SIGHUP"), - 2 => Ok("SIGINT"), - 3 => Ok("SIGQUIT"), - 4 => Ok("SIGILL"), - 5 => Ok("SIGTRAP"), - 6 => Ok("SIGABRT"), - 7 => Ok("SIGEMT"), - 8 => Ok("SIGFPE"), - 9 => Ok("SIGKILL"), - 10 => Ok("SIGBUS"), - 11 => Ok("SIGSEGV"), - 12 => Ok("SIGSYS"), - 13 => Ok("SIGPIPE"), - 14 => Ok("SIGALRM"), - 15 => Ok("SIGTERM"), - 16 => Ok("SIGUSR1"), - 17 => Ok("SIGUSR2"), - 18 => Ok("SIGCHLD"), - 19 => Ok("SIGPWR"), - 20 => Ok("SIGWINCH"), - 21 => Ok("SIGURG"), - 22 => Ok("SIGPOLL"), - 23 => Ok("SIGSTOP"), - 24 => Ok("SIGTSTP"), - 25 => Ok("SIGCONT"), - 26 => Ok("SIGTTIN"), - 27 => Ok("SIGTTOU"), - 28 => Ok("SIGVTALRM"), - 29 => Ok("SIGPROF"), - 30 => Ok("SIGXCPU"), - 31 => Ok("SIGXFSZ"), - 32 => Ok("SIGWAITING"), - 33 => Ok("SIGLWP"), - 34 => Ok("SIGFREEZE"), - 35 => Ok("SIGTHAW"), - 36 => Ok("SIGCANCEL"), - 37 => Ok("SIGLOST"), - 38 => Ok("SIGXRES"), - 39 => Ok("SIGJVM1"), - 40 => Ok("SIGJVM2"), - _ => Err(type_error(format!("Invalid signal : {}", s))), - } -} - -#[cfg(target_os = "windows")] -pub fn signal_str_to_int(s: &str) -> Result<libc::c_int, AnyError> { - match s { - "SIGINT" => Ok(2), - "SIGBREAK" => Ok(21), - _ => Err(type_error( - "Windows only supports ctrl-c (SIGINT) and ctrl-break (SIGBREAK).", - )), - } -} +signal_dict!( + (1, "SIGHUP"), + (2, "SIGINT"), + (3, "SIGQUIT"), + (4, "SIGILL"), + (5, "SIGTRAP"), + (6, "SIGABRT" | "SIGIOT"), + (7, "SIGEMT"), + (8, "SIGFPE"), + (9, "SIGKILL"), + (10, "SIGBUS"), + (11, "SIGSEGV"), + (12, "SIGSYS"), + (13, "SIGPIPE"), + (14, "SIGALRM"), + (15, "SIGTERM"), + (16, "SIGUSR1"), + (17, "SIGUSR2"), + (18, "SIGCHLD"), + (19, "SIGPWR"), + (20, "SIGWINCH"), + (21, "SIGURG"), + (22, "SIGPOLL"), + (23, "SIGSTOP"), + (24, "SIGTSTP"), + (25, "SIGCONT"), + (26, "SIGTTIN"), + (27, "SIGTTOU"), + (28, "SIGVTALRM"), + (29, "SIGPROF"), + (30, "SIGXCPU"), + (31, "SIGXFSZ"), + (32, "SIGWAITING"), + (33, "SIGLWP"), + (34, "SIGFREEZE"), + (35, "SIGTHAW"), + (36, "SIGCANCEL"), + (37, "SIGLOST"), + (38, "SIGXRES"), + (39, "SIGJVM1"), + (40, "SIGJVM2") +); #[cfg(target_os = "windows")] -pub fn signal_int_to_str(s: libc::c_int) -> Result<&'static str, AnyError> { - match s { - 2 => Ok("SIGINT"), - 21 => Ok("SIGBREAK"), - _ => Err(type_error( - "Windows only supports ctrl-c (SIGINT) and ctrl-break (SIGBREAK).", - )), - } -} +signal_dict!((2, "SIGINT"), (21, "SIGBREAK")); #[cfg(unix)] #[op2(fast)] @@ -586,12 +399,10 @@ pub fn signal_int_to_str(s: libc::c_int) -> Result<&'static str, AnyError> { fn op_signal_bind( state: &mut OpState, #[string] sig: &str, -) -> Result<ResourceId, AnyError> { +) -> Result<ResourceId, SignalError> { let signo = signal_str_to_int(sig)?; if signal_hook_registry::FORBIDDEN.contains(&signo) { - return Err(type_error(format!( - "Binding to signal '{sig}' is not allowed", - ))); + return Err(SignalError::SignalNotAllowed(sig.to_string())); } let signal = AsyncRefCell::new(signal(SignalKind::from_raw(signo))?); @@ -625,7 +436,7 @@ fn op_signal_bind( fn op_signal_bind( state: &mut OpState, #[string] sig: &str, -) -> Result<ResourceId, AnyError> { +) -> Result<ResourceId, SignalError> { let signo = signal_str_to_int(sig)?; let resource = SignalStreamResource { signal: AsyncRefCell::new(match signo { @@ -649,7 +460,7 @@ fn op_signal_bind( async fn op_signal_poll( state: Rc<RefCell<OpState>>, #[smi] rid: ResourceId, -) -> Result<bool, AnyError> { +) -> Result<bool, deno_core::error::AnyError> { let resource = state .borrow_mut() .resource_table @@ -668,7 +479,7 @@ async fn op_signal_poll( pub fn op_signal_unbind( state: &mut OpState, #[smi] rid: ResourceId, -) -> Result<(), AnyError> { +) -> Result<(), deno_core::error::AnyError> { let resource = state.resource_table.take::<SignalStreamResource>(rid)?; #[cfg(unix)] diff --git a/runtime/ops/tty.rs b/runtime/ops/tty.rs index 5b49e3a24..7849185fa 100644 --- a/runtime/ops/tty.rs +++ b/runtime/ops/tty.rs @@ -2,7 +2,6 @@ use std::io::Error; -use deno_core::error::AnyError; use deno_core::op2; use deno_core::OpState; use rustyline::config::Configurer; @@ -64,6 +63,19 @@ deno_core::extension!( }, ); +#[derive(Debug, thiserror::Error)] +pub enum TtyError { + #[error(transparent)] + Resource(deno_core::error::AnyError), + #[error("{0}")] + Io(#[from] std::io::Error), + #[cfg(unix)] + #[error(transparent)] + Nix(nix::Error), + #[error(transparent)] + Other(deno_core::error::AnyError), +} + // ref: <https://learn.microsoft.com/en-us/windows/console/setconsolemode> #[cfg(windows)] const COOKED_MODE: DWORD = @@ -90,8 +102,11 @@ fn op_set_raw( rid: u32, is_raw: bool, cbreak: bool, -) -> Result<(), AnyError> { - let handle_or_fd = state.resource_table.get_fd(rid)?; +) -> Result<(), TtyError> { + let handle_or_fd = state + .resource_table + .get_fd(rid) + .map_err(TtyError::Resource)?; // From https://github.com/kkawakam/rustyline/blob/master/src/tty/windows.rs // and https://github.com/kkawakam/rustyline/blob/master/src/tty/unix.rs @@ -107,7 +122,7 @@ fn op_set_raw( let handle = handle_or_fd; if cbreak { - return Err(deno_core::error::not_supported()); + return Err(TtyError::Other(deno_core::error::not_supported())); } let mut original_mode: DWORD = 0; @@ -115,7 +130,7 @@ fn op_set_raw( if unsafe { consoleapi::GetConsoleMode(handle, &mut original_mode) } == FALSE { - return Err(Error::last_os_error().into()); + return Err(TtyError::Io(Error::last_os_error())); } let new_mode = if is_raw { @@ -185,7 +200,7 @@ fn op_set_raw( winapi::um::wincon::WriteConsoleInputW(handle, &record, 1, &mut 0) } == FALSE { - return Err(Error::last_os_error().into()); + return Err(TtyError::Io(Error::last_os_error())); } /* Wait for read thread to acknowledge the cancellation to ensure that nothing @@ -199,7 +214,7 @@ fn op_set_raw( // SAFETY: winapi call if unsafe { consoleapi::SetConsoleMode(handle, new_mode) } == FALSE { - return Err(Error::last_os_error().into()); + return Err(TtyError::Io(Error::last_os_error())); } Ok(()) @@ -244,14 +259,16 @@ fn op_set_raw( let tty_mode_store = state.borrow::<TtyModeStore>().clone(); let previous_mode = tty_mode_store.get(rid); - let raw_fd = handle_or_fd; + // SAFETY: Nix crate requires value to implement the AsFd trait + let raw_fd = unsafe { std::os::fd::BorrowedFd::borrow_raw(handle_or_fd) }; if is_raw { let mut raw = match previous_mode { Some(mode) => mode, None => { // Save original mode. - let original_mode = termios::tcgetattr(raw_fd)?; + let original_mode = + termios::tcgetattr(raw_fd).map_err(TtyError::Nix)?; tty_mode_store.set(rid, original_mode.clone()); original_mode } @@ -273,11 +290,13 @@ fn op_set_raw( } raw.control_chars[termios::SpecialCharacterIndices::VMIN as usize] = 1; raw.control_chars[termios::SpecialCharacterIndices::VTIME as usize] = 0; - termios::tcsetattr(raw_fd, termios::SetArg::TCSADRAIN, &raw)?; + termios::tcsetattr(raw_fd, termios::SetArg::TCSADRAIN, &raw) + .map_err(TtyError::Nix)?; } else { // Try restore saved mode. if let Some(mode) = tty_mode_store.take(rid) { - termios::tcsetattr(raw_fd, termios::SetArg::TCSADRAIN, &mode)?; + termios::tcsetattr(raw_fd, termios::SetArg::TCSADRAIN, &mode) + .map_err(TtyError::Nix)?; } } @@ -289,13 +308,16 @@ fn op_set_raw( fn op_console_size( state: &mut OpState, #[buffer] result: &mut [u32], -) -> Result<(), AnyError> { +) -> Result<(), TtyError> { fn check_console_size( state: &mut OpState, result: &mut [u32], rid: u32, - ) -> Result<(), AnyError> { - let fd = state.resource_table.get_fd(rid)?; + ) -> Result<(), TtyError> { + let fd = state + .resource_table + .get_fd(rid) + .map_err(TtyError::Resource)?; let size = console_size_from_fd(fd)?; result[0] = size.cols; result[1] = size.rows; @@ -418,7 +440,7 @@ mod tests { pub fn op_read_line_prompt( #[string] prompt_text: &str, #[string] default_value: &str, -) -> Result<Option<String>, AnyError> { +) -> Result<Option<String>, ReadlineError> { let mut editor = Editor::<(), rustyline::history::DefaultHistory>::new() .expect("Failed to create editor."); @@ -438,6 +460,6 @@ pub fn op_read_line_prompt( Ok(None) } Err(ReadlineError::Eof) => Ok(None), - Err(err) => Err(err.into()), + Err(err) => Err(err), } } diff --git a/runtime/ops/utils.rs b/runtime/ops/utils.rs deleted file mode 100644 index d5ce61c1f..000000000 --- a/runtime/ops/utils.rs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. - -use deno_core::error::custom_error; -use deno_core::error::AnyError; - -/// A utility function to map OsStrings to Strings -pub fn into_string(s: std::ffi::OsString) -> Result<String, AnyError> { - s.into_string().map_err(|s| { - let message = format!("File name or path {s:?} is not valid UTF-8"); - custom_error("InvalidData", message) - }) -} diff --git a/runtime/ops/web_worker.rs b/runtime/ops/web_worker.rs index 0ed76ebd5..d0c3eea66 100644 --- a/runtime/ops/web_worker.rs +++ b/runtime/ops/web_worker.rs @@ -4,15 +4,16 @@ mod sync_fetch; use crate::web_worker::WebWorkerInternalHandle; use crate::web_worker::WebWorkerType; -use deno_core::error::AnyError; use deno_core::op2; use deno_core::CancelFuture; use deno_core::OpState; use deno_web::JsMessageData; +use deno_web::MessagePortError; use std::cell::RefCell; use std::rc::Rc; use self::sync_fetch::op_worker_sync_fetch; +pub use sync_fetch::SyncFetchError; deno_core::extension!( deno_web_worker, @@ -30,17 +31,16 @@ deno_core::extension!( fn op_worker_post_message( state: &mut OpState, #[serde] data: JsMessageData, -) -> Result<(), AnyError> { +) -> Result<(), MessagePortError> { let handle = state.borrow::<WebWorkerInternalHandle>().clone(); - handle.port.send(state, data)?; - Ok(()) + handle.port.send(state, data) } #[op2(async(lazy), fast)] #[serde] async fn op_worker_recv_message( state: Rc<RefCell<OpState>>, -) -> Result<Option<JsMessageData>, AnyError> { +) -> Result<Option<JsMessageData>, MessagePortError> { let handle = { let state = state.borrow(); state.borrow::<WebWorkerInternalHandle>().clone() diff --git a/runtime/ops/web_worker/sync_fetch.rs b/runtime/ops/web_worker/sync_fetch.rs index cdb151a86..d1f133d3d 100644 --- a/runtime/ops/web_worker/sync_fetch.rs +++ b/runtime/ops/web_worker/sync_fetch.rs @@ -4,15 +4,13 @@ use std::sync::Arc; use crate::web_worker::WebWorkerInternalHandle; use crate::web_worker::WebWorkerType; -use deno_core::error::type_error; -use deno_core::error::AnyError; use deno_core::futures::StreamExt; use deno_core::op2; use deno_core::url::Url; use deno_core::OpState; use deno_fetch::data_url::DataUrl; +use deno_fetch::FetchError; use deno_web::BlobStore; -use deno_websocket::DomExceptionNetworkError; use http_body_util::BodyExt; use hyper::body::Bytes; use serde::Deserialize; @@ -27,6 +25,32 @@ fn mime_type_essence(mime_type: &str) -> String { essence.trim().to_ascii_lowercase() } +#[derive(Debug, thiserror::Error)] +pub enum SyncFetchError { + #[error("Blob URLs are not supported in this context.")] + BlobUrlsNotSupportedInContext, + #[error("{0}")] + Io(#[from] std::io::Error), + #[error("Invalid script URL")] + InvalidScriptUrl, + #[error("http status error: {0}")] + InvalidStatusCode(http::StatusCode), + #[error("Classic scripts with scheme {0}: are not supported in workers")] + ClassicScriptSchemeUnsupportedInWorkers(String), + #[error("{0}")] + InvalidUri(#[from] http::uri::InvalidUri), + #[error("Invalid MIME type {0:?}.")] + InvalidMimeType(String), + #[error("Missing MIME type.")] + MissingMimeType, + #[error(transparent)] + Fetch(#[from] FetchError), + #[error(transparent)] + Join(#[from] tokio::task::JoinError), + #[error(transparent)] + Other(deno_core::error::AnyError), +} + #[derive(Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct SyncFetchScript { @@ -40,21 +64,22 @@ pub fn op_worker_sync_fetch( state: &mut OpState, #[serde] scripts: Vec<String>, loose_mime_checks: bool, -) -> Result<Vec<SyncFetchScript>, AnyError> { +) -> Result<Vec<SyncFetchScript>, SyncFetchError> { let handle = state.borrow::<WebWorkerInternalHandle>().clone(); assert_eq!(handle.worker_type, WebWorkerType::Classic); // it's not safe to share a client across tokio runtimes, so create a fresh one // https://github.com/seanmonstar/reqwest/issues/1148#issuecomment-910868788 let options = state.borrow::<deno_fetch::Options>().clone(); - let client = deno_fetch::create_client_from_options(&options)?; + let client = deno_fetch::create_client_from_options(&options) + .map_err(FetchError::ClientCreate)?; // TODO(andreubotella) It's not good to throw an exception related to blob // URLs when none of the script URLs use the blob scheme. // Also, in which contexts are blob URLs not supported? let blob_store = state .try_borrow::<Arc<BlobStore>>() - .ok_or_else(|| type_error("Blob URLs are not supported in this context."))? + .ok_or(SyncFetchError::BlobUrlsNotSupportedInContext)? .clone(); // TODO(andreubotella): make the below thread into a resource that can be @@ -74,7 +99,7 @@ pub fn op_worker_sync_fetch( let blob_store = blob_store.clone(); deno_core::unsync::spawn(async move { let script_url = Url::parse(&script) - .map_err(|_| type_error("Invalid script URL"))?; + .map_err(|_| SyncFetchError::InvalidScriptUrl)?; let mut loose_mime_checks = loose_mime_checks; let (body, mime_type, res_url) = match script_url.scheme() { @@ -86,15 +111,13 @@ pub fn op_worker_sync_fetch( ); *req.uri_mut() = script_url.as_str().parse()?; - let resp = client.send(req).await?; + let resp = + client.send(req).await.map_err(FetchError::ClientSend)?; if resp.status().is_client_error() || resp.status().is_server_error() { - return Err(type_error(format!( - "http status error: {}", - resp.status() - ))); + return Err(SyncFetchError::InvalidStatusCode(resp.status())); } // TODO(andreubotella) Properly run fetch's "extract a MIME type". @@ -107,42 +130,45 @@ pub fn op_worker_sync_fetch( // Always check the MIME type with HTTP(S). loose_mime_checks = false; - let body = resp.collect().await?.to_bytes(); + let body = resp + .collect() + .await + .map_err(SyncFetchError::Other)? + .to_bytes(); (body, mime_type, script) } "data" => { - let data_url = DataUrl::process(&script) - .map_err(|e| type_error(format!("{e:?}")))?; + let data_url = + DataUrl::process(&script).map_err(FetchError::DataUrl)?; let mime_type = { let mime = data_url.mime_type(); format!("{}/{}", mime.type_, mime.subtype) }; - let (body, _) = data_url - .decode_to_vec() - .map_err(|e| type_error(format!("{e:?}")))?; + let (body, _) = + data_url.decode_to_vec().map_err(FetchError::Base64)?; (Bytes::from(body), Some(mime_type), script) } "blob" => { - let blob = - blob_store.get_object_url(script_url).ok_or_else(|| { - type_error("Blob for the given URL not found.") - })?; + let blob = blob_store + .get_object_url(script_url) + .ok_or(FetchError::BlobNotFound)?; let mime_type = mime_type_essence(&blob.media_type); - let body = blob.read_all().await?; + let body = blob.read_all().await; (Bytes::from(body), Some(mime_type), script) } _ => { - return Err(type_error(format!( - "Classic scripts with scheme {}: are not supported in workers.", - script_url.scheme() - ))) + return Err( + SyncFetchError::ClassicScriptSchemeUnsupportedInWorkers( + script_url.scheme().to_string(), + ), + ) } }; @@ -151,18 +177,11 @@ pub fn op_worker_sync_fetch( match mime_type.as_deref() { Some("application/javascript" | "text/javascript") => {} Some(mime_type) => { - return Err( - DomExceptionNetworkError { - msg: format!("Invalid MIME type {mime_type:?}."), - } - .into(), - ) - } - None => { - return Err( - DomExceptionNetworkError::new("Missing MIME type.").into(), - ) + return Err(SyncFetchError::InvalidMimeType( + mime_type.to_string(), + )) } + None => return Err(SyncFetchError::MissingMimeType), } } diff --git a/runtime/ops/worker_host.rs b/runtime/ops/worker_host.rs index b9fd06654..521284a6a 100644 --- a/runtime/ops/worker_host.rs +++ b/runtime/ops/worker_host.rs @@ -10,7 +10,6 @@ use crate::web_worker::WorkerControlEvent; use crate::web_worker::WorkerId; use crate::web_worker::WorkerMetadata; use crate::worker::FormatJsErrorFn; -use deno_core::error::AnyError; use deno_core::op2; use deno_core::serde::Deserialize; use deno_core::CancelFuture; @@ -21,6 +20,7 @@ use deno_permissions::ChildPermissionsArg; use deno_permissions::PermissionsContainer; use deno_web::deserialize_js_transferables; use deno_web::JsMessageData; +use deno_web::MessagePortError; use log::debug; use std::cell::RefCell; use std::collections::HashMap; @@ -118,6 +118,20 @@ pub struct CreateWorkerArgs { close_on_idle: bool, } +#[derive(Debug, thiserror::Error)] +pub enum CreateWorkerError { + #[error("Classic workers are not supported.")] + ClassicWorkers, + #[error(transparent)] + Permission(deno_permissions::ChildPermissionError), + #[error(transparent)] + ModuleResolution(#[from] deno_core::ModuleResolutionError), + #[error(transparent)] + MessagePort(#[from] MessagePortError), + #[error("{0}")] + Io(#[from] std::io::Error), +} + /// Create worker as the host #[op2] #[serde] @@ -125,7 +139,7 @@ fn op_create_worker( state: &mut OpState, #[serde] args: CreateWorkerArgs, #[serde] maybe_worker_metadata: Option<JsMessageData>, -) -> Result<WorkerId, AnyError> { +) -> Result<WorkerId, CreateWorkerError> { let specifier = args.specifier.clone(); let maybe_source_code = if args.has_source_code { Some(args.source_code.clone()) @@ -136,12 +150,7 @@ fn op_create_worker( let worker_type = args.worker_type; if let WebWorkerType::Classic = worker_type { if let TestingFeaturesEnabled(false) = state.borrow() { - return Err( - deno_webstorage::DomExceptionNotSupportedError::new( - "Classic workers are not supported.", - ) - .into(), - ); + return Err(CreateWorkerError::ClassicWorkers); } } @@ -155,7 +164,9 @@ fn op_create_worker( let parent_permissions = state.borrow_mut::<PermissionsContainer>(); let worker_permissions = if let Some(child_permissions_arg) = args.permissions { - parent_permissions.create_child_permissions(child_permissions_arg)? + parent_permissions + .create_child_permissions(child_permissions_arg) + .map_err(CreateWorkerError::Permission)? } else { parent_permissions.clone() }; @@ -167,9 +178,8 @@ fn op_create_worker( let module_specifier = deno_core::resolve_url(&specifier)?; let worker_name = args_name.unwrap_or_default(); - let (handle_sender, handle_receiver) = std::sync::mpsc::sync_channel::< - Result<SendableWebWorkerHandle, AnyError>, - >(1); + let (handle_sender, handle_receiver) = + std::sync::mpsc::sync_channel::<SendableWebWorkerHandle>(1); // Setup new thread let thread_builder = std::thread::Builder::new().name(format!("{worker_id}")); @@ -203,7 +213,7 @@ fn op_create_worker( }); // Send thread safe handle from newly created worker to host thread - handle_sender.send(Ok(external_handle)).unwrap(); + handle_sender.send(external_handle).unwrap(); drop(handle_sender); // At this point the only method of communication with host @@ -219,7 +229,7 @@ fn op_create_worker( })?; // Receive WebWorkerHandle from newly created worker - let worker_handle = handle_receiver.recv().unwrap()?; + let worker_handle = handle_receiver.recv().unwrap(); let worker_thread = WorkerThread { worker_handle: worker_handle.into(), @@ -292,7 +302,7 @@ fn close_channel( async fn op_host_recv_ctrl( state: Rc<RefCell<OpState>>, #[serde] id: WorkerId, -) -> Result<WorkerControlEvent, AnyError> { +) -> WorkerControlEvent { let (worker_handle, cancel_handle) = { let state = state.borrow(); let workers_table = state.borrow::<WorkersTable>(); @@ -301,7 +311,7 @@ async fn op_host_recv_ctrl( (handle.worker_handle.clone(), handle.cancel_handle.clone()) } else { // If handle was not found it means worker has already shutdown - return Ok(WorkerControlEvent::Close); + return WorkerControlEvent::Close; } }; @@ -310,22 +320,21 @@ async fn op_host_recv_ctrl( .or_cancel(cancel_handle) .await; match maybe_event { - Ok(Ok(Some(event))) => { + Ok(Some(event)) => { // Terminal error means that worker should be removed from worker table. if let WorkerControlEvent::TerminalError(_) = &event { close_channel(state, id, WorkerChannel::Ctrl); } - Ok(event) + event } - Ok(Ok(None)) => { + Ok(None) => { // If there was no event from worker it means it has already been closed. close_channel(state, id, WorkerChannel::Ctrl); - Ok(WorkerControlEvent::Close) + WorkerControlEvent::Close } - Ok(Err(err)) => Err(err), Err(_) => { // The worker was terminated. - Ok(WorkerControlEvent::Close) + WorkerControlEvent::Close } } } @@ -335,7 +344,7 @@ async fn op_host_recv_ctrl( async fn op_host_recv_message( state: Rc<RefCell<OpState>>, #[serde] id: WorkerId, -) -> Result<Option<JsMessageData>, AnyError> { +) -> Result<Option<JsMessageData>, MessagePortError> { let (worker_handle, cancel_handle) = { let s = state.borrow(); let workers_table = s.borrow::<WorkersTable>(); @@ -374,7 +383,7 @@ fn op_host_post_message( state: &mut OpState, #[serde] id: WorkerId, #[serde] data: JsMessageData, -) -> Result<(), AnyError> { +) -> Result<(), MessagePortError> { if let Some(worker_thread) = state.borrow::<WorkersTable>().get(&id) { debug!("post message to worker {}", id); let worker_handle = worker_thread.worker_handle.clone(); diff --git a/runtime/permissions.rs b/runtime/permissions.rs index fa62227e0..e8460e03f 100644 --- a/runtime/permissions.rs +++ b/runtime/permissions.rs @@ -3,9 +3,6 @@ use std::path::Path; use std::path::PathBuf; -use deno_core::anyhow::bail; -use deno_core::anyhow::Context; -use deno_core::error::AnyError; use deno_path_util::normalize_path; use deno_permissions::AllowRunDescriptor; use deno_permissions::AllowRunDescriptorParseResult; @@ -15,9 +12,12 @@ use deno_permissions::FfiDescriptor; use deno_permissions::ImportDescriptor; use deno_permissions::NetDescriptor; use deno_permissions::PathQueryDescriptor; +use deno_permissions::PathResolveError; use deno_permissions::ReadDescriptor; +use deno_permissions::RunDescriptorParseError; use deno_permissions::RunQueryDescriptor; use deno_permissions::SysDescriptor; +use deno_permissions::SysDescriptorParseError; use deno_permissions::WriteDescriptor; #[derive(Debug)] @@ -30,9 +30,9 @@ impl RuntimePermissionDescriptorParser { Self { fs } } - fn resolve_from_cwd(&self, path: &str) -> Result<PathBuf, AnyError> { + fn resolve_from_cwd(&self, path: &str) -> Result<PathBuf, PathResolveError> { if path.is_empty() { - bail!("Empty path is not allowed"); + return Err(PathResolveError::EmptyPath); } let path = Path::new(path); if path.is_absolute() { @@ -43,12 +43,11 @@ impl RuntimePermissionDescriptorParser { } } - fn resolve_cwd(&self) -> Result<PathBuf, AnyError> { + fn resolve_cwd(&self) -> Result<PathBuf, PathResolveError> { self .fs .cwd() - .map_err(|e| e.into_io_error()) - .context("failed resolving cwd") + .map_err(|e| PathResolveError::CwdResolve(e.into_io_error())) } } @@ -58,37 +57,37 @@ impl deno_permissions::PermissionDescriptorParser fn parse_read_descriptor( &self, text: &str, - ) -> Result<ReadDescriptor, AnyError> { + ) -> Result<ReadDescriptor, PathResolveError> { Ok(ReadDescriptor(self.resolve_from_cwd(text)?)) } fn parse_write_descriptor( &self, text: &str, - ) -> Result<WriteDescriptor, AnyError> { + ) -> Result<WriteDescriptor, PathResolveError> { Ok(WriteDescriptor(self.resolve_from_cwd(text)?)) } fn parse_net_descriptor( &self, text: &str, - ) -> Result<NetDescriptor, AnyError> { + ) -> Result<NetDescriptor, deno_permissions::NetDescriptorParseError> { NetDescriptor::parse(text) } fn parse_import_descriptor( &self, text: &str, - ) -> Result<ImportDescriptor, AnyError> { + ) -> Result<ImportDescriptor, deno_permissions::NetDescriptorParseError> { ImportDescriptor::parse(text) } fn parse_env_descriptor( &self, text: &str, - ) -> Result<EnvDescriptor, AnyError> { + ) -> Result<EnvDescriptor, deno_permissions::EnvDescriptorParseError> { if text.is_empty() { - Err(AnyError::msg("Empty env not allowed")) + Err(deno_permissions::EnvDescriptorParseError) } else { Ok(EnvDescriptor::new(text)) } @@ -97,9 +96,9 @@ impl deno_permissions::PermissionDescriptorParser fn parse_sys_descriptor( &self, text: &str, - ) -> Result<deno_permissions::SysDescriptor, AnyError> { + ) -> Result<SysDescriptor, SysDescriptorParseError> { if text.is_empty() { - Err(AnyError::msg("Empty sys not allowed")) + Err(SysDescriptorParseError::Empty) } else { Ok(SysDescriptor::parse(text.to_string())?) } @@ -108,21 +107,21 @@ impl deno_permissions::PermissionDescriptorParser fn parse_allow_run_descriptor( &self, text: &str, - ) -> Result<AllowRunDescriptorParseResult, AnyError> { + ) -> Result<AllowRunDescriptorParseResult, RunDescriptorParseError> { Ok(AllowRunDescriptor::parse(text, &self.resolve_cwd()?)?) } fn parse_deny_run_descriptor( &self, text: &str, - ) -> Result<DenyRunDescriptor, AnyError> { + ) -> Result<DenyRunDescriptor, PathResolveError> { Ok(DenyRunDescriptor::parse(text, &self.resolve_cwd()?)) } fn parse_ffi_descriptor( &self, text: &str, - ) -> Result<deno_permissions::FfiDescriptor, AnyError> { + ) -> Result<FfiDescriptor, PathResolveError> { Ok(FfiDescriptor(self.resolve_from_cwd(text)?)) } @@ -131,7 +130,7 @@ impl deno_permissions::PermissionDescriptorParser fn parse_path_query( &self, path: &str, - ) -> Result<PathQueryDescriptor, AnyError> { + ) -> Result<PathQueryDescriptor, PathResolveError> { Ok(PathQueryDescriptor { resolved: self.resolve_from_cwd(path)?, requested: path.to_string(), @@ -141,11 +140,12 @@ impl deno_permissions::PermissionDescriptorParser fn parse_run_query( &self, requested: &str, - ) -> Result<RunQueryDescriptor, AnyError> { + ) -> Result<RunQueryDescriptor, RunDescriptorParseError> { if requested.is_empty() { - bail!("Empty run query is not allowed"); + return Err(RunDescriptorParseError::EmptyRunQuery); } RunQueryDescriptor::parse(requested) + .map_err(RunDescriptorParseError::PathResolve) } } diff --git a/runtime/permissions/Cargo.toml b/runtime/permissions/Cargo.toml index 8f0b0dd3d..e088eb8ce 100644 --- a/runtime/permissions/Cargo.toml +++ b/runtime/permissions/Cargo.toml @@ -2,7 +2,7 @@ [package] name = "deno_permissions" -version = "0.31.0" +version = "0.37.0" authors.workspace = true edition.workspace = true license.workspace = true @@ -23,6 +23,7 @@ log.workspace = true once_cell.workspace = true percent-encoding = { version = "2.3.1", features = [] } serde.workspace = true +thiserror.workspace = true which.workspace = true [target.'cfg(windows)'.dependencies] diff --git a/runtime/permissions/lib.rs b/runtime/permissions/lib.rs index 2904242da..71ef7d228 100644 --- a/runtime/permissions/lib.rs +++ b/runtime/permissions/lib.rs @@ -1,11 +1,5 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -use deno_core::anyhow::bail; -use deno_core::anyhow::Context; -use deno_core::error::custom_error; -use deno_core::error::type_error; -use deno_core::error::uri_error; -use deno_core::error::AnyError; use deno_core::parking_lot::Mutex; use deno_core::serde::de; use deno_core::serde::Deserialize; @@ -35,11 +29,28 @@ use std::sync::Arc; pub mod prompter; use prompter::permission_prompt; -use prompter::PromptResponse; use prompter::PERMISSION_EMOJI; pub use prompter::set_prompt_callbacks; +pub use prompter::set_prompter; +pub use prompter::PermissionPrompter; pub use prompter::PromptCallback; +pub use prompter::PromptResponse; + +#[derive(Debug, thiserror::Error)] +#[error("Requires {access}, {}", format_permission_error(.name))] +pub struct PermissionDeniedError { + pub access: String, + pub name: &'static str, +} + +fn format_permission_error(name: &'static str) -> String { + if is_standalone() { + format!("specify the required permissions during compilation using `deno compile --allow-{name}`") + } else { + format!("run again with the --allow-{name} flag") + } +} /// Fast exit from permission check routines if this permission /// is in the "fully-granted" state. @@ -102,7 +113,10 @@ impl From<bool> for AllowPartial { impl PermissionState { #[inline(always)] - fn log_perm_access(name: &str, info: impl FnOnce() -> Option<String>) { + fn log_perm_access( + name: &'static str, + info: impl FnOnce() -> Option<String>, + ) { // Eliminates log overhead (when logging is disabled), // log_enabled!(Debug) check in a hot path still has overhead // TODO(AaronO): generalize or upstream this optimization @@ -118,53 +132,47 @@ impl PermissionState { } } - fn fmt_access(name: &str, info: impl FnOnce() -> Option<String>) -> String { + fn fmt_access( + name: &'static str, + info: impl FnOnce() -> Option<String>, + ) -> String { format!( "{} access{}", name, - info() - .map(|info| { format!(" to {info}") }) - .unwrap_or_default(), + info().map(|info| format!(" to {info}")).unwrap_or_default(), ) } - fn error(name: &str, info: impl FnOnce() -> Option<String>) -> AnyError { - let msg = if is_standalone() { - format!( - "Requires {}, specify the required permissions during compilation using `deno compile --allow-{}`", - Self::fmt_access(name, info), - name - ) - } else { - format!( - "Requires {}, run again with the --allow-{} flag", - Self::fmt_access(name, info), - name - ) - }; - custom_error("NotCapable", msg) + fn error( + name: &'static str, + info: impl FnOnce() -> Option<String>, + ) -> PermissionDeniedError { + PermissionDeniedError { + access: Self::fmt_access(name, info), + name, + } } /// Check the permission state. bool is whether a prompt was issued. #[inline] fn check( self, - name: &str, + name: &'static str, api_name: Option<&str>, info: Option<&str>, prompt: bool, - ) -> (Result<(), AnyError>, bool, bool) { + ) -> (Result<(), PermissionDeniedError>, bool, bool) { self.check2(name, api_name, || info.map(|s| s.to_string()), prompt) } #[inline] fn check2( self, - name: &str, + name: &'static str, api_name: Option<&str>, info: impl Fn() -> Option<String>, prompt: bool, - ) -> (Result<(), AnyError>, bool, bool) { + ) -> (Result<(), PermissionDeniedError>, bool, bool) { match self { PermissionState::Granted => { Self::log_perm_access(name, info); @@ -244,7 +252,7 @@ impl UnitPermission { self.state } - pub fn check(&mut self) -> Result<(), AnyError> { + pub fn check(&mut self) -> Result<(), PermissionDeniedError> { let (result, prompted, _is_allow_all) = self.state.check(self.name, None, None, self.prompt); if prompted { @@ -260,7 +268,7 @@ impl UnitPermission { fn create_child_permissions( &mut self, flag: ChildUnitPermissionArg, - ) -> Result<Self, AnyError> { + ) -> Result<Self, ChildPermissionError> { let mut perm = self.clone(); match flag { ChildUnitPermissionArg::Inherit => { @@ -268,7 +276,7 @@ impl UnitPermission { } ChildUnitPermissionArg::Granted => { if self.check().is_err() { - return Err(escalation_error()); + return Err(ChildPermissionError::Escalation); } perm.state = PermissionState::Granted; } @@ -325,7 +333,7 @@ pub trait QueryDescriptor: Debug { &self, perm: &mut UnaryPermission<Self>, api_name: Option<&str>, - ) -> Result<(), AnyError>; + ) -> Result<(), PermissionDeniedError>; fn matches_allow(&self, other: &Self::AllowDesc) -> bool; fn matches_deny(&self, other: &Self::DenyDesc) -> bool; @@ -400,7 +408,7 @@ impl<TQuery: QueryDescriptor> UnaryPermission<TQuery> { pub fn check_all_api( &mut self, api_name: Option<&str>, - ) -> Result<(), AnyError> { + ) -> Result<(), PermissionDeniedError> { skip_check_if_is_permission_fully_granted!(self); self.check_desc(None, false, api_name) } @@ -410,7 +418,7 @@ impl<TQuery: QueryDescriptor> UnaryPermission<TQuery> { desc: Option<&TQuery>, assert_non_partial: bool, api_name: Option<&str>, - ) -> Result<(), AnyError> { + ) -> Result<(), PermissionDeniedError> { let (result, prompted, is_allow_all) = self .query_desc(desc, AllowPartial::from(!assert_non_partial)) .check2( @@ -597,11 +605,14 @@ impl<TQuery: QueryDescriptor> UnaryPermission<TQuery> { } } - fn create_child_permissions( + fn create_child_permissions<E>( &mut self, flag: ChildUnaryPermissionArg, - parse: impl Fn(&str) -> Result<Option<TQuery::AllowDesc>, AnyError>, - ) -> Result<UnaryPermission<TQuery>, AnyError> { + parse: impl Fn(&str) -> Result<Option<TQuery::AllowDesc>, E>, + ) -> Result<UnaryPermission<TQuery>, ChildPermissionError> + where + ChildPermissionError: From<E>, + { let mut perms = Self::default(); match flag { @@ -610,7 +621,7 @@ impl<TQuery: QueryDescriptor> UnaryPermission<TQuery> { } ChildUnaryPermissionArg::Granted => { if self.check_all_api(None).is_err() { - return Err(escalation_error()); + return Err(ChildPermissionError::Escalation); } perms.granted_global = true; } @@ -619,13 +630,13 @@ impl<TQuery: QueryDescriptor> UnaryPermission<TQuery> { perms.granted_list = granted_list .iter() .filter_map(|i| parse(i).transpose()) - .collect::<Result<_, _>>()?; + .collect::<Result<_, E>>()?; if !perms.granted_list.iter().all(|desc| { TQuery::from_allow(desc) .check_in_permission(self, None) .is_ok() }) { - return Err(escalation_error()); + return Err(ChildPermissionError::Escalation); } } } @@ -696,7 +707,7 @@ impl QueryDescriptor for ReadQueryDescriptor { &self, perm: &mut UnaryPermission<Self>, api_name: Option<&str>, - ) -> Result<(), AnyError> { + ) -> Result<(), PermissionDeniedError> { skip_check_if_is_permission_fully_granted!(perm); perm.check_desc(Some(self), true, api_name) } @@ -759,7 +770,7 @@ impl QueryDescriptor for WriteQueryDescriptor { &self, perm: &mut UnaryPermission<Self>, api_name: Option<&str>, - ) -> Result<(), AnyError> { + ) -> Result<(), PermissionDeniedError> { skip_check_if_is_permission_fully_granted!(perm); perm.check_desc(Some(self), true, api_name) } @@ -794,22 +805,37 @@ pub enum Host { Ip(IpAddr), } +#[derive(Debug, thiserror::Error)] +pub enum HostParseError { + #[error("invalid IPv6 address: '{0}'")] + InvalidIpv6(String), + #[error("invalid host: '{0}'")] + InvalidHost(String), + #[error("invalid empty host: '{0}'")] + InvalidEmptyHost(String), + #[error("invalid host '{host}': {error}")] + Fqdn { + #[source] + error: fqdn::Error, + host: String, + }, +} + impl Host { - // TODO(bartlomieju): rewrite to not use `AnyError` but a specific error implementations - fn parse(s: &str) -> Result<Self, AnyError> { + fn parse(s: &str) -> Result<Self, HostParseError> { if s.starts_with('[') && s.ends_with(']') { let ip = s[1..s.len() - 1] .parse::<Ipv6Addr>() - .map_err(|_| uri_error(format!("invalid IPv6 address: '{s}'")))?; + .map_err(|_| HostParseError::InvalidIpv6(s.to_string()))?; return Ok(Host::Ip(IpAddr::V6(ip))); } let (without_trailing_dot, has_trailing_dot) = s.strip_suffix('.').map_or((s, false), |s| (s, true)); if let Ok(ip) = without_trailing_dot.parse::<IpAddr>() { if has_trailing_dot { - return Err(uri_error(format!( - "invalid host: '{without_trailing_dot}'" - ))); + return Err(HostParseError::InvalidHost( + without_trailing_dot.to_string(), + )); } Ok(Host::Ip(ip)) } else { @@ -820,11 +846,13 @@ impl Host { }; let fqdn = { use std::str::FromStr; - FQDN::from_str(&lower) - .with_context(|| format!("invalid host: '{s}'"))? + FQDN::from_str(&lower).map_err(|e| HostParseError::Fqdn { + error: e, + host: s.to_string(), + })? }; if fqdn.is_root() { - return Err(uri_error(format!("invalid empty host: '{s}'"))); + return Err(HostParseError::InvalidEmptyHost(s.to_string())); } Ok(Host::Fqdn(fqdn)) } @@ -868,7 +896,7 @@ impl QueryDescriptor for NetDescriptor { &self, perm: &mut UnaryPermission<Self>, api_name: Option<&str>, - ) -> Result<(), AnyError> { + ) -> Result<(), PermissionDeniedError> { skip_check_if_is_permission_fully_granted!(perm); perm.check_desc(Some(self), false, api_name) } @@ -894,39 +922,72 @@ impl QueryDescriptor for NetDescriptor { } } -// TODO(bartlomieju): rewrite to not use `AnyError` but a specific error implementations +#[derive(Debug, thiserror::Error)] +pub enum NetDescriptorParseError { + #[error("invalid value '{0}': URLs are not supported, only domains and ips")] + Url(String), + #[error("invalid IPv6 address in '{hostname}': '{ip}'")] + InvalidIpv6 { hostname: String, ip: String }, + #[error("invalid port in '{hostname}': '{port}'")] + InvalidPort { hostname: String, port: String }, + #[error("invalid host: '{0}'")] + InvalidHost(String), + #[error("invalid empty port in '{0}'")] + EmptyPort(String), + #[error("ipv6 addresses must be enclosed in square brackets: '{0}'")] + Ipv6MissingSquareBrackets(String), + #[error("{0}")] + Host(#[from] HostParseError), +} + +#[derive(Debug, thiserror::Error)] +pub enum NetDescriptorFromUrlParseError { + #[error("Missing host in url: '{0}'")] + MissingHost(Url), + #[error("{0}")] + Host(#[from] HostParseError), +} + impl NetDescriptor { - pub fn parse(hostname: &str) -> Result<Self, AnyError> { + pub fn parse(hostname: &str) -> Result<Self, NetDescriptorParseError> { if hostname.starts_with("http://") || hostname.starts_with("https://") { - return Err(uri_error(format!("invalid value '{hostname}': URLs are not supported, only domains and ips"))); + return Err(NetDescriptorParseError::Url(hostname.to_string())); } // If this is a IPv6 address enclosed in square brackets, parse it as such. if hostname.starts_with('[') { if let Some((ip, after)) = hostname.split_once(']') { let ip = ip[1..].parse::<Ipv6Addr>().map_err(|_| { - uri_error(format!("invalid IPv6 address in '{hostname}': '{ip}'")) + NetDescriptorParseError::InvalidIpv6 { + hostname: hostname.to_string(), + ip: ip.to_string(), + } })?; let port = if let Some(port) = after.strip_prefix(':') { let port = port.parse::<u16>().map_err(|_| { - uri_error(format!("invalid port in '{hostname}': '{port}'")) + NetDescriptorParseError::InvalidPort { + hostname: hostname.to_string(), + port: port.to_string(), + } })?; Some(port) } else if after.is_empty() { None } else { - return Err(uri_error(format!("invalid host: '{hostname}'"))); + return Err(NetDescriptorParseError::InvalidHost( + hostname.to_string(), + )); }; return Ok(NetDescriptor(Host::Ip(IpAddr::V6(ip)), port)); } else { - return Err(uri_error(format!("invalid host: '{hostname}'"))); + return Err(NetDescriptorParseError::InvalidHost(hostname.to_string())); } } // Otherwise it is an IPv4 address or a FQDN with an optional port. let (host, port) = match hostname.split_once(':') { Some((_, "")) => { - return Err(uri_error(format!("invalid empty port in '{hostname}'"))); + return Err(NetDescriptorParseError::EmptyPort(hostname.to_string())); } Some((host, port)) => (host, port), None => (hostname, ""), @@ -941,11 +1002,14 @@ impl NetDescriptor { // should give them a hint. There are always at least two colons in an // IPv6 address, so this heuristic finds likely a bare IPv6 address. if port.contains(':') { - uri_error(format!( - "ipv6 addresses must be enclosed in square brackets: '{hostname}'" - )) + NetDescriptorParseError::Ipv6MissingSquareBrackets( + hostname.to_string(), + ) } else { - uri_error(format!("invalid port in '{hostname}': '{port}'")) + NetDescriptorParseError::InvalidPort { + hostname: hostname.to_string(), + port: port.to_string(), + } } })?; Some(port) @@ -954,10 +1018,10 @@ impl NetDescriptor { Ok(NetDescriptor(host, port)) } - pub fn from_url(url: &Url) -> Result<Self, AnyError> { - let host = url - .host_str() - .ok_or_else(|| type_error(format!("Missing host in url: '{}'", url)))?; + pub fn from_url(url: &Url) -> Result<Self, NetDescriptorFromUrlParseError> { + let host = url.host_str().ok_or_else(|| { + NetDescriptorFromUrlParseError::MissingHost(url.clone()) + })?; let host = Host::parse(host)?; let port = url.port_or_known_default(); Ok(NetDescriptor(host, port)) @@ -1009,7 +1073,7 @@ impl QueryDescriptor for ImportDescriptor { &self, perm: &mut UnaryPermission<Self>, api_name: Option<&str>, - ) -> Result<(), AnyError> { + ) -> Result<(), PermissionDeniedError> { skip_check_if_is_permission_fully_granted!(perm); perm.check_desc(Some(self), false, api_name) } @@ -1036,15 +1100,19 @@ impl QueryDescriptor for ImportDescriptor { } impl ImportDescriptor { - pub fn parse(specifier: &str) -> Result<Self, AnyError> { + pub fn parse(specifier: &str) -> Result<Self, NetDescriptorParseError> { Ok(ImportDescriptor(NetDescriptor::parse(specifier)?)) } - pub fn from_url(url: &Url) -> Result<Self, AnyError> { + pub fn from_url(url: &Url) -> Result<Self, NetDescriptorFromUrlParseError> { Ok(ImportDescriptor(NetDescriptor::from_url(url)?)) } } +#[derive(Debug, thiserror::Error)] +#[error("Empty env not allowed")] +pub struct EnvDescriptorParseError; + #[derive(Clone, Eq, PartialEq, Hash, Debug)] pub struct EnvDescriptor(EnvVarName); @@ -1082,7 +1150,7 @@ impl QueryDescriptor for EnvDescriptor { &self, perm: &mut UnaryPermission<Self>, api_name: Option<&str>, - ) -> Result<(), AnyError> { + ) -> Result<(), PermissionDeniedError> { skip_check_if_is_permission_fully_granted!(perm); perm.check_desc(Some(self), false, api_name) } @@ -1129,14 +1197,25 @@ pub enum RunQueryDescriptor { Name(String), } +#[derive(Debug, thiserror::Error)] +pub enum PathResolveError { + #[error("failed resolving cwd: {0}")] + CwdResolve(#[source] std::io::Error), + #[error("Empty path is not allowed")] + EmptyPath, +} + impl RunQueryDescriptor { - pub fn parse(requested: &str) -> Result<RunQueryDescriptor, AnyError> { + pub fn parse( + requested: &str, + ) -> Result<RunQueryDescriptor, PathResolveError> { if is_path(requested) { let path = PathBuf::from(requested); let resolved = if path.is_absolute() { normalize_path(path) } else { - let cwd = std::env::current_dir().context("failed resolving cwd")?; + let cwd = + std::env::current_dir().map_err(PathResolveError::CwdResolve)?; normalize_path(cwd.join(path)) }; Ok(RunQueryDescriptor::Path { @@ -1208,7 +1287,7 @@ impl QueryDescriptor for RunQueryDescriptor { &self, perm: &mut UnaryPermission<Self>, api_name: Option<&str>, - ) -> Result<(), AnyError> { + ) -> Result<(), PermissionDeniedError> { skip_check_if_is_permission_fully_granted!(perm); perm.check_desc(Some(self), false, api_name) } @@ -1278,6 +1357,16 @@ pub enum AllowRunDescriptorParseResult { Descriptor(AllowRunDescriptor), } +#[derive(Debug, thiserror::Error)] +pub enum RunDescriptorParseError { + #[error("{0}")] + Which(#[from] which::Error), + #[error("{0}")] + PathResolve(#[from] PathResolveError), + #[error("Empty run query is not allowed")] + EmptyRunQuery, +} + #[derive(Debug, Clone, Hash, Eq, PartialEq)] pub struct AllowRunDescriptor(pub PathBuf); @@ -1358,17 +1447,29 @@ fn denies_run_name(name: &str, cmd_path: &Path) -> bool { suffix.is_empty() || suffix.starts_with('.') } +#[derive(Debug, thiserror::Error)] +pub enum SysDescriptorParseError { + #[error("unknown system info kind \"{0}\"")] + InvalidKind(String), // TypeError + #[error("Empty sys not allowed")] + Empty, // Error +} + #[derive(Clone, Eq, PartialEq, Hash, Debug)] pub struct SysDescriptor(String); impl SysDescriptor { - pub fn parse(kind: String) -> Result<Self, AnyError> { + pub fn parse(kind: String) -> Result<Self, SysDescriptorParseError> { match kind.as_str() { - "hostname" | "osRelease" | "osUptime" | "loadavg" + "hostname" | "inspector" | "osRelease" | "osUptime" | "loadavg" | "networkInterfaces" | "systemMemoryInfo" | "uid" | "gid" | "cpus" - | "homedir" | "getegid" | "username" | "statfs" | "getPriority" - | "setPriority" => Ok(Self(kind)), - _ => Err(type_error(format!("unknown system info kind \"{kind}\""))), + | "homedir" | "getegid" | "statfs" | "getPriority" | "setPriority" + | "userInfo" => Ok(Self(kind)), + + // the underlying permission check changed to `userInfo` to better match the API, + // alias this to avoid breaking existing projects with `--allow-sys=username` + "username" => Ok(Self("userInfo".into())), + _ => Err(SysDescriptorParseError::InvalidKind(kind)), } } @@ -1405,7 +1506,7 @@ impl QueryDescriptor for SysDescriptor { &self, perm: &mut UnaryPermission<Self>, api_name: Option<&str>, - ) -> Result<(), AnyError> { + ) -> Result<(), PermissionDeniedError> { skip_check_if_is_permission_fully_granted!(perm); perm.check_desc(Some(self), false, api_name) } @@ -1466,7 +1567,7 @@ impl QueryDescriptor for FfiQueryDescriptor { &self, perm: &mut UnaryPermission<Self>, api_name: Option<&str>, - ) -> Result<(), AnyError> { + ) -> Result<(), PermissionDeniedError> { skip_check_if_is_permission_fully_granted!(perm); perm.check_desc(Some(self), true, api_name) } @@ -1518,7 +1619,7 @@ impl UnaryPermission<ReadQueryDescriptor> { &mut self, desc: &ReadQueryDescriptor, api_name: Option<&str>, - ) -> Result<(), AnyError> { + ) -> Result<(), PermissionDeniedError> { skip_check_if_is_permission_fully_granted!(self); self.check_desc(Some(desc), true, api_name) } @@ -1528,12 +1629,15 @@ impl UnaryPermission<ReadQueryDescriptor> { &mut self, desc: &ReadQueryDescriptor, api_name: Option<&str>, - ) -> Result<(), AnyError> { + ) -> Result<(), PermissionDeniedError> { skip_check_if_is_permission_fully_granted!(self); self.check_desc(Some(desc), false, api_name) } - pub fn check_all(&mut self, api_name: Option<&str>) -> Result<(), AnyError> { + pub fn check_all( + &mut self, + api_name: Option<&str>, + ) -> Result<(), PermissionDeniedError> { skip_check_if_is_permission_fully_granted!(self); self.check_desc(None, false, api_name) } @@ -1562,7 +1666,7 @@ impl UnaryPermission<WriteQueryDescriptor> { &mut self, path: &WriteQueryDescriptor, api_name: Option<&str>, - ) -> Result<(), AnyError> { + ) -> Result<(), PermissionDeniedError> { skip_check_if_is_permission_fully_granted!(self); self.check_desc(Some(path), true, api_name) } @@ -1572,12 +1676,15 @@ impl UnaryPermission<WriteQueryDescriptor> { &mut self, path: &WriteQueryDescriptor, api_name: Option<&str>, - ) -> Result<(), AnyError> { + ) -> Result<(), PermissionDeniedError> { skip_check_if_is_permission_fully_granted!(self); self.check_desc(Some(path), false, api_name) } - pub fn check_all(&mut self, api_name: Option<&str>) -> Result<(), AnyError> { + pub fn check_all( + &mut self, + api_name: Option<&str>, + ) -> Result<(), PermissionDeniedError> { skip_check_if_is_permission_fully_granted!(self); self.check_desc(None, false, api_name) } @@ -1600,12 +1707,12 @@ impl UnaryPermission<NetDescriptor> { &mut self, host: &NetDescriptor, api_name: Option<&str>, - ) -> Result<(), AnyError> { + ) -> Result<(), PermissionDeniedError> { skip_check_if_is_permission_fully_granted!(self); self.check_desc(Some(host), false, api_name) } - pub fn check_all(&mut self) -> Result<(), AnyError> { + pub fn check_all(&mut self) -> Result<(), PermissionDeniedError> { skip_check_if_is_permission_fully_granted!(self); self.check_desc(None, false, None) } @@ -1631,12 +1738,12 @@ impl UnaryPermission<ImportDescriptor> { &mut self, host: &ImportDescriptor, api_name: Option<&str>, - ) -> Result<(), AnyError> { + ) -> Result<(), PermissionDeniedError> { skip_check_if_is_permission_fully_granted!(self); self.check_desc(Some(host), false, api_name) } - pub fn check_all(&mut self) -> Result<(), AnyError> { + pub fn check_all(&mut self) -> Result<(), PermissionDeniedError> { skip_check_if_is_permission_fully_granted!(self); self.check_desc(None, false, None) } @@ -1662,12 +1769,12 @@ impl UnaryPermission<EnvDescriptor> { &mut self, env: &str, api_name: Option<&str>, - ) -> Result<(), AnyError> { + ) -> Result<(), PermissionDeniedError> { skip_check_if_is_permission_fully_granted!(self); self.check_desc(Some(&EnvDescriptor::new(env)), false, api_name) } - pub fn check_all(&mut self) -> Result<(), AnyError> { + pub fn check_all(&mut self) -> Result<(), PermissionDeniedError> { skip_check_if_is_permission_fully_granted!(self); self.check_desc(None, false, None) } @@ -1690,12 +1797,12 @@ impl UnaryPermission<SysDescriptor> { &mut self, kind: &SysDescriptor, api_name: Option<&str>, - ) -> Result<(), AnyError> { + ) -> Result<(), PermissionDeniedError> { skip_check_if_is_permission_fully_granted!(self); self.check_desc(Some(kind), false, api_name) } - pub fn check_all(&mut self) -> Result<(), AnyError> { + pub fn check_all(&mut self) -> Result<(), PermissionDeniedError> { skip_check_if_is_permission_fully_granted!(self); self.check_desc(None, false, None) } @@ -1724,11 +1831,14 @@ impl UnaryPermission<RunQueryDescriptor> { &mut self, cmd: &RunQueryDescriptor, api_name: Option<&str>, - ) -> Result<(), AnyError> { + ) -> Result<(), PermissionDeniedError> { self.check_desc(Some(cmd), false, api_name) } - pub fn check_all(&mut self, api_name: Option<&str>) -> Result<(), AnyError> { + pub fn check_all( + &mut self, + api_name: Option<&str>, + ) -> Result<(), PermissionDeniedError> { self.check_desc(None, false, api_name) } @@ -1771,7 +1881,7 @@ impl UnaryPermission<FfiQueryDescriptor> { &mut self, path: &FfiQueryDescriptor, api_name: Option<&str>, - ) -> Result<(), AnyError> { + ) -> Result<(), PermissionDeniedError> { skip_check_if_is_permission_fully_granted!(self); self.check_desc(Some(path), true, api_name) } @@ -1779,12 +1889,12 @@ impl UnaryPermission<FfiQueryDescriptor> { pub fn check_partial( &mut self, path: Option<&FfiQueryDescriptor>, - ) -> Result<(), AnyError> { + ) -> Result<(), PermissionDeniedError> { skip_check_if_is_permission_fully_granted!(self); self.check_desc(path, false, None) } - pub fn check_all(&mut self) -> Result<(), AnyError> { + pub fn check_all(&mut self) -> Result<(), PermissionDeniedError> { skip_check_if_is_permission_fully_granted!(self); self.check_desc(None, false, Some("all")) } @@ -1824,23 +1934,39 @@ pub struct PermissionsOptions { pub prompt: bool, } +#[derive(Debug, thiserror::Error)] +pub enum PermissionsFromOptionsError { + #[error("{0}")] + PathResolve(#[from] PathResolveError), + #[error("{0}")] + SysDescriptorParse(#[from] SysDescriptorParseError), + #[error("{0}")] + NetDescriptorParse(#[from] NetDescriptorParseError), + #[error("{0}")] + EnvDescriptorParse(#[from] EnvDescriptorParseError), + #[error("{0}")] + RunDescriptorParse(#[from] RunDescriptorParseError), + #[error("Empty command name not allowed in --allow-run=...")] + RunEmptyCommandName, +} + impl Permissions { pub fn new_unary<TQuery>( allow_list: Option<HashSet<TQuery::AllowDesc>>, deny_list: Option<HashSet<TQuery::DenyDesc>>, prompt: bool, - ) -> Result<UnaryPermission<TQuery>, AnyError> + ) -> UnaryPermission<TQuery> where TQuery: QueryDescriptor, { - Ok(UnaryPermission::<TQuery> { + UnaryPermission::<TQuery> { granted_global: global_from_option(allow_list.as_ref()), granted_list: allow_list.unwrap_or_default(), flag_denied_global: global_from_option(deny_list.as_ref()), flag_denied_list: deny_list.unwrap_or_default(), prompt, ..Default::default() - }) + } } pub const fn new_all(allow_state: bool) -> UnitPermission { @@ -1856,15 +1982,15 @@ impl Permissions { pub fn from_options( parser: &dyn PermissionDescriptorParser, opts: &PermissionsOptions, - ) -> Result<Self, AnyError> { + ) -> Result<Self, PermissionsFromOptionsError> { fn resolve_allow_run( parser: &dyn PermissionDescriptorParser, allow_run: &[String], - ) -> Result<HashSet<AllowRunDescriptor>, AnyError> { + ) -> Result<HashSet<AllowRunDescriptor>, PermissionsFromOptionsError> { let mut new_allow_run = HashSet::with_capacity(allow_run.len()); for unresolved in allow_run { if unresolved.is_empty() { - bail!("Empty command name not allowed in --allow-run=...") + return Err(PermissionsFromOptionsError::RunEmptyCommandName); } match parser.parse_allow_run_descriptor(unresolved)? { AllowRunDescriptorParseResult::Descriptor(descriptor) => { @@ -1883,10 +2009,13 @@ impl Permissions { Ok(new_allow_run) } - fn parse_maybe_vec<T: Eq + PartialEq + Hash>( + fn parse_maybe_vec<T: Eq + PartialEq + Hash, E>( items: Option<&[String]>, - parse: impl Fn(&str) -> Result<T, AnyError>, - ) -> Result<Option<HashSet<T>>, AnyError> { + parse: impl Fn(&str) -> Result<T, E>, + ) -> Result<Option<HashSet<T>>, PermissionsFromOptionsError> + where + PermissionsFromOptionsError: From<E>, + { match items { Some(items) => Ok(Some( items @@ -1938,14 +2067,14 @@ impl Permissions { parser.parse_read_descriptor(item) })?, opts.prompt, - )?, + ), write: Permissions::new_unary( parse_maybe_vec(opts.allow_write.as_deref(), |item| { parser.parse_write_descriptor(item) })?, deny_write, opts.prompt, - )?, + ), net: Permissions::new_unary( parse_maybe_vec(opts.allow_net.as_deref(), |item| { parser.parse_net_descriptor(item) @@ -1954,7 +2083,7 @@ impl Permissions { parser.parse_net_descriptor(item) })?, opts.prompt, - )?, + ), env: Permissions::new_unary( parse_maybe_vec(opts.allow_env.as_deref(), |item| { parser.parse_env_descriptor(item) @@ -1963,7 +2092,7 @@ impl Permissions { parser.parse_env_descriptor(text) })?, opts.prompt, - )?, + ), sys: Permissions::new_unary( parse_maybe_vec(opts.allow_sys.as_deref(), |text| { parser.parse_sys_descriptor(text) @@ -1972,14 +2101,14 @@ impl Permissions { parser.parse_sys_descriptor(text) })?, opts.prompt, - )?, + ), run: Permissions::new_unary( allow_run, parse_maybe_vec(opts.deny_run.as_deref(), |text| { parser.parse_deny_run_descriptor(text) })?, opts.prompt, - )?, + ), ffi: Permissions::new_unary( parse_maybe_vec(opts.allow_ffi.as_deref(), |text| { parser.parse_ffi_descriptor(text) @@ -1988,14 +2117,14 @@ impl Permissions { parser.parse_ffi_descriptor(text) })?, opts.prompt, - )?, + ), import: Permissions::new_unary( parse_maybe_vec(opts.allow_import.as_deref(), |item| { parser.parse_import_descriptor(item) })?, None, opts.prompt, - )?, + ), all: Permissions::new_all(opts.allow_all), }) } @@ -2027,14 +2156,14 @@ impl Permissions { fn none(prompt: bool) -> Self { Self { - read: Permissions::new_unary(None, None, prompt).unwrap(), - write: Permissions::new_unary(None, None, prompt).unwrap(), - net: Permissions::new_unary(None, None, prompt).unwrap(), - env: Permissions::new_unary(None, None, prompt).unwrap(), - sys: Permissions::new_unary(None, None, prompt).unwrap(), - run: Permissions::new_unary(None, None, prompt).unwrap(), - ffi: Permissions::new_unary(None, None, prompt).unwrap(), - import: Permissions::new_unary(None, None, prompt).unwrap(), + read: Permissions::new_unary(None, None, prompt), + write: Permissions::new_unary(None, None, prompt), + net: Permissions::new_unary(None, None, prompt), + env: Permissions::new_unary(None, None, prompt), + sys: Permissions::new_unary(None, None, prompt), + run: Permissions::new_unary(None, None, prompt), + ffi: Permissions::new_unary(None, None, prompt), + import: Permissions::new_unary(None, None, prompt), all: Permissions::new_all(false), } } @@ -2046,6 +2175,38 @@ pub enum CheckSpecifierKind { Dynamic, } +#[derive(Debug, thiserror::Error)] +pub enum ChildPermissionError { + #[error("Can't escalate parent thread permissions")] + Escalation, + #[error("{0}")] + PathResolve(#[from] PathResolveError), + #[error("{0}")] + NetDescriptorParse(#[from] NetDescriptorParseError), + #[error("{0}")] + EnvDescriptorParse(#[from] EnvDescriptorParseError), + #[error("{0}")] + SysDescriptorParse(#[from] SysDescriptorParseError), + #[error("{0}")] + RunDescriptorParse(#[from] RunDescriptorParseError), +} + +#[derive(Debug, thiserror::Error)] +pub enum PermissionCheckError { + #[error(transparent)] + PermissionDenied(#[from] PermissionDeniedError), + #[error("Invalid file path.\n Specifier: {0}")] + InvalidFilePath(Url), + #[error(transparent)] + NetDescriptorForUrlParse(#[from] NetDescriptorFromUrlParseError), + #[error(transparent)] + SysDescriptorParse(#[from] SysDescriptorParseError), + #[error(transparent)] + PathResolve(#[from] PathResolveError), + #[error(transparent)] + HostParse(#[from] HostParseError), +} + /// Wrapper struct for `Permissions` that can be shared across threads. /// /// We need a way to have internal mutability for permissions as they might get @@ -2078,7 +2239,7 @@ impl PermissionsContainer { pub fn create_child_permissions( &self, child_permissions_arg: ChildPermissionsArg, - ) -> Result<PermissionsContainer, AnyError> { + ) -> Result<PermissionsContainer, ChildPermissionError> { fn is_granted_unary(arg: &ChildUnaryPermissionArg) -> bool { match arg { ChildUnaryPermissionArg::Inherit | ChildUnaryPermissionArg::Granted => { @@ -2116,48 +2277,71 @@ impl PermissionsContainer { // WARNING: When adding a permission here, ensure it is handled // in the worker_perms.all block above - worker_perms.read = inner - .read - .create_child_permissions(child_permissions_arg.read, |text| { - Ok(Some(self.descriptor_parser.parse_read_descriptor(text)?)) - })?; - worker_perms.write = inner - .write - .create_child_permissions(child_permissions_arg.write, |text| { - Ok(Some(self.descriptor_parser.parse_write_descriptor(text)?)) - })?; - worker_perms.import = inner - .import - .create_child_permissions(child_permissions_arg.import, |text| { - Ok(Some(self.descriptor_parser.parse_import_descriptor(text)?)) - })?; - worker_perms.net = inner - .net - .create_child_permissions(child_permissions_arg.net, |text| { - Ok(Some(self.descriptor_parser.parse_net_descriptor(text)?)) - })?; - worker_perms.env = inner - .env - .create_child_permissions(child_permissions_arg.env, |text| { - Ok(Some(self.descriptor_parser.parse_env_descriptor(text)?)) - })?; - worker_perms.sys = inner - .sys - .create_child_permissions(child_permissions_arg.sys, |text| { - Ok(Some(self.descriptor_parser.parse_sys_descriptor(text)?)) - })?; + worker_perms.read = inner.read.create_child_permissions( + child_permissions_arg.read, + |text| { + Ok::<_, PathResolveError>(Some( + self.descriptor_parser.parse_read_descriptor(text)?, + )) + }, + )?; + worker_perms.write = inner.write.create_child_permissions( + child_permissions_arg.write, + |text| { + Ok::<_, PathResolveError>(Some( + self.descriptor_parser.parse_write_descriptor(text)?, + )) + }, + )?; + worker_perms.import = inner.import.create_child_permissions( + child_permissions_arg.import, + |text| { + Ok::<_, NetDescriptorParseError>(Some( + self.descriptor_parser.parse_import_descriptor(text)?, + )) + }, + )?; + worker_perms.net = inner.net.create_child_permissions( + child_permissions_arg.net, + |text| { + Ok::<_, NetDescriptorParseError>(Some( + self.descriptor_parser.parse_net_descriptor(text)?, + )) + }, + )?; + worker_perms.env = inner.env.create_child_permissions( + child_permissions_arg.env, + |text| { + Ok::<_, EnvDescriptorParseError>(Some( + self.descriptor_parser.parse_env_descriptor(text)?, + )) + }, + )?; + worker_perms.sys = inner.sys.create_child_permissions( + child_permissions_arg.sys, + |text| { + Ok::<_, SysDescriptorParseError>(Some( + self.descriptor_parser.parse_sys_descriptor(text)?, + )) + }, + )?; worker_perms.run = inner.run.create_child_permissions( child_permissions_arg.run, |text| match self.descriptor_parser.parse_allow_run_descriptor(text)? { - AllowRunDescriptorParseResult::Unresolved(_) => Ok(None), + AllowRunDescriptorParseResult::Unresolved(_) => { + Ok::<_, RunDescriptorParseError>(None) + } AllowRunDescriptorParseResult::Descriptor(desc) => Ok(Some(desc)), }, )?; - worker_perms.ffi = inner - .ffi - .create_child_permissions(child_permissions_arg.ffi, |text| { - Ok(Some(self.descriptor_parser.parse_ffi_descriptor(text)?)) - })?; + worker_perms.ffi = inner.ffi.create_child_permissions( + child_permissions_arg.ffi, + |text| { + Ok::<_, PathResolveError>(Some( + self.descriptor_parser.parse_ffi_descriptor(text)?, + )) + }, + )?; Ok(PermissionsContainer::new( self.descriptor_parser.clone(), @@ -2170,7 +2354,7 @@ impl PermissionsContainer { &self, specifier: &ModuleSpecifier, kind: CheckSpecifierKind, - ) -> Result<(), AnyError> { + ) -> Result<(), PermissionCheckError> { let mut inner = self.inner.lock(); match specifier.scheme() { "file" => { @@ -2179,17 +2363,20 @@ impl PermissionsContainer { } match url_to_file_path(specifier) { - Ok(path) => inner.read.check( - &PathQueryDescriptor { - requested: path.to_string_lossy().into_owned(), - resolved: path, - } - .into_read(), - Some("import()"), - ), - Err(_) => Err(uri_error(format!( - "Invalid file path.\n Specifier: {specifier}" - ))), + Ok(path) => inner + .read + .check( + &PathQueryDescriptor { + requested: path.to_string_lossy().into_owned(), + resolved: path, + } + .into_read(), + Some("import()"), + ) + .map_err(PermissionCheckError::PermissionDenied), + Err(_) => { + Err(PermissionCheckError::InvalidFilePath(specifier.clone())) + } } } "data" => Ok(()), @@ -2214,7 +2401,7 @@ impl PermissionsContainer { &self, path: &str, api_name: &str, - ) -> Result<PathBuf, AnyError> { + ) -> Result<PathBuf, PermissionCheckError> { self.check_read_with_api_name(path, Some(api_name)) } @@ -2224,7 +2411,7 @@ impl PermissionsContainer { &self, path: &str, api_name: Option<&str>, - ) -> Result<PathBuf, AnyError> { + ) -> Result<PathBuf, PermissionCheckError> { let mut inner = self.inner.lock(); let inner = &mut inner.read; if inner.is_allow_all() { @@ -2242,7 +2429,7 @@ impl PermissionsContainer { &self, path: &'a Path, api_name: Option<&str>, - ) -> Result<Cow<'a, Path>, AnyError> { + ) -> Result<Cow<'a, Path>, PermissionCheckError> { let mut inner = self.inner.lock(); let inner = &mut inner.read; if inner.is_allow_all() { @@ -2266,7 +2453,7 @@ impl PermissionsContainer { path: &Path, display: &str, api_name: &str, - ) -> Result<(), AnyError> { + ) -> Result<(), PermissionCheckError> { let mut inner = self.inner.lock(); let inner = &mut inner.read; skip_check_if_is_permission_fully_granted!(inner); @@ -2277,12 +2464,17 @@ impl PermissionsContainer { } .into_read(), Some(api_name), - ) + )?; + Ok(()) } #[inline(always)] - pub fn check_read_all(&self, api_name: &str) -> Result<(), AnyError> { - self.inner.lock().read.check_all(Some(api_name)) + pub fn check_read_all( + &self, + api_name: &str, + ) -> Result<(), PermissionCheckError> { + self.inner.lock().read.check_all(Some(api_name))?; + Ok(()) } #[inline(always)] @@ -2296,7 +2488,7 @@ impl PermissionsContainer { &self, path: &str, api_name: &str, - ) -> Result<PathBuf, AnyError> { + ) -> Result<PathBuf, PermissionCheckError> { self.check_write_with_api_name(path, Some(api_name)) } @@ -2306,7 +2498,7 @@ impl PermissionsContainer { &self, path: &str, api_name: Option<&str>, - ) -> Result<PathBuf, AnyError> { + ) -> Result<PathBuf, PermissionCheckError> { let mut inner = self.inner.lock(); let inner = &mut inner.write; if inner.is_allow_all() { @@ -2324,7 +2516,7 @@ impl PermissionsContainer { &self, path: &'a Path, api_name: &str, - ) -> Result<Cow<'a, Path>, AnyError> { + ) -> Result<Cow<'a, Path>, PermissionCheckError> { let mut inner = self.inner.lock(); let inner = &mut inner.write; if inner.is_allow_all() { @@ -2341,8 +2533,12 @@ impl PermissionsContainer { } #[inline(always)] - pub fn check_write_all(&self, api_name: &str) -> Result<(), AnyError> { - self.inner.lock().write.check_all(Some(api_name)) + pub fn check_write_all( + &self, + api_name: &str, + ) -> Result<(), PermissionCheckError> { + self.inner.lock().write.check_all(Some(api_name))?; + Ok(()) } /// As `check_write()`, but permission error messages will anonymize the path @@ -2353,7 +2549,7 @@ impl PermissionsContainer { path: &Path, display: &str, api_name: &str, - ) -> Result<(), AnyError> { + ) -> Result<(), PermissionCheckError> { let mut inner = self.inner.lock(); let inner = &mut inner.write; skip_check_if_is_permission_fully_granted!(inner); @@ -2364,7 +2560,8 @@ impl PermissionsContainer { } .into_write(), Some(api_name), - ) + )?; + Ok(()) } #[inline(always)] @@ -2372,7 +2569,7 @@ impl PermissionsContainer { &mut self, path: &str, api_name: &str, - ) -> Result<PathBuf, AnyError> { + ) -> Result<PathBuf, PermissionCheckError> { let mut inner = self.inner.lock(); let inner = &mut inner.write; if inner.is_allow_all() { @@ -2389,13 +2586,18 @@ impl PermissionsContainer { &mut self, cmd: &RunQueryDescriptor, api_name: &str, - ) -> Result<(), AnyError> { - self.inner.lock().run.check(cmd, Some(api_name)) + ) -> Result<(), PermissionCheckError> { + self.inner.lock().run.check(cmd, Some(api_name))?; + Ok(()) } #[inline(always)] - pub fn check_run_all(&mut self, api_name: &str) -> Result<(), AnyError> { - self.inner.lock().run.check_all(Some(api_name)) + pub fn check_run_all( + &mut self, + api_name: &str, + ) -> Result<(), PermissionCheckError> { + self.inner.lock().run.check_all(Some(api_name))?; + Ok(()) } #[inline(always)] @@ -2404,38 +2606,50 @@ impl PermissionsContainer { } #[inline(always)] - pub fn check_sys(&self, kind: &str, api_name: &str) -> Result<(), AnyError> { + pub fn check_sys( + &self, + kind: &str, + api_name: &str, + ) -> Result<(), PermissionCheckError> { self.inner.lock().sys.check( &self.descriptor_parser.parse_sys_descriptor(kind)?, Some(api_name), - ) + )?; + Ok(()) } #[inline(always)] - pub fn check_env(&mut self, var: &str) -> Result<(), AnyError> { - self.inner.lock().env.check(var, None) + pub fn check_env(&mut self, var: &str) -> Result<(), PermissionCheckError> { + self.inner.lock().env.check(var, None)?; + Ok(()) } #[inline(always)] - pub fn check_env_all(&mut self) -> Result<(), AnyError> { - self.inner.lock().env.check_all() + pub fn check_env_all(&mut self) -> Result<(), PermissionCheckError> { + self.inner.lock().env.check_all()?; + Ok(()) } #[inline(always)] - pub fn check_sys_all(&mut self) -> Result<(), AnyError> { - self.inner.lock().sys.check_all() + pub fn check_sys_all(&mut self) -> Result<(), PermissionCheckError> { + self.inner.lock().sys.check_all()?; + Ok(()) } #[inline(always)] - pub fn check_ffi_all(&mut self) -> Result<(), AnyError> { - self.inner.lock().ffi.check_all() + pub fn check_ffi_all(&mut self) -> Result<(), PermissionCheckError> { + self.inner.lock().ffi.check_all()?; + Ok(()) } /// This checks to see if the allow-all flag was passed, not whether all /// permissions are enabled! #[inline(always)] - pub fn check_was_allow_all_flag_passed(&mut self) -> Result<(), AnyError> { - self.inner.lock().all.check() + pub fn check_was_allow_all_flag_passed( + &mut self, + ) -> Result<(), PermissionCheckError> { + self.inner.lock().all.check()?; + Ok(()) } /// Checks special file access, returning the failed permission type if @@ -2547,13 +2761,14 @@ impl PermissionsContainer { &mut self, url: &Url, api_name: &str, - ) -> Result<(), AnyError> { + ) -> Result<(), PermissionCheckError> { let mut inner = self.inner.lock(); if inner.net.is_allow_all() { return Ok(()); } let desc = self.descriptor_parser.parse_net_descriptor_from_url(url)?; - inner.net.check(&desc, Some(api_name)) + inner.net.check(&desc, Some(api_name))?; + Ok(()) } #[inline(always)] @@ -2561,17 +2776,21 @@ impl PermissionsContainer { &mut self, host: &(T, Option<u16>), api_name: &str, - ) -> Result<(), AnyError> { + ) -> Result<(), PermissionCheckError> { let mut inner = self.inner.lock(); let inner = &mut inner.net; skip_check_if_is_permission_fully_granted!(inner); let hostname = Host::parse(host.0.as_ref())?; let descriptor = NetDescriptor(hostname, host.1); - inner.check(&descriptor, Some(api_name)) + inner.check(&descriptor, Some(api_name))?; + Ok(()) } #[inline(always)] - pub fn check_ffi(&mut self, path: &str) -> Result<PathBuf, AnyError> { + pub fn check_ffi( + &mut self, + path: &str, + ) -> Result<PathBuf, PermissionCheckError> { let mut inner = self.inner.lock(); let inner = &mut inner.ffi; if inner.is_allow_all() { @@ -2585,14 +2804,15 @@ impl PermissionsContainer { #[must_use = "the resolved return value to mitigate time-of-check to time-of-use issues"] #[inline(always)] - pub fn check_ffi_partial_no_path(&mut self) -> Result<(), AnyError> { + pub fn check_ffi_partial_no_path( + &mut self, + ) -> Result<(), PermissionCheckError> { let mut inner = self.inner.lock(); let inner = &mut inner.ffi; - if inner.is_allow_all() { - Ok(()) - } else { - inner.check_partial(None) + if !inner.is_allow_all() { + inner.check_partial(None)?; } + Ok(()) } #[must_use = "the resolved return value to mitigate time-of-check to time-of-use issues"] @@ -2600,7 +2820,7 @@ impl PermissionsContainer { pub fn check_ffi_partial_with_path( &mut self, path: &str, - ) -> Result<PathBuf, AnyError> { + ) -> Result<PathBuf, PermissionCheckError> { let mut inner = self.inner.lock(); let inner = &mut inner.ffi; if inner.is_allow_all() { @@ -2618,7 +2838,7 @@ impl PermissionsContainer { pub fn query_read( &self, path: Option<&str>, - ) -> Result<PermissionState, AnyError> { + ) -> Result<PermissionState, PathResolveError> { let inner = self.inner.lock(); let permission = &inner.read; if permission.is_allow_all() { @@ -2628,7 +2848,7 @@ impl PermissionsContainer { permission.query( path .map(|path| { - Result::<_, AnyError>::Ok( + Ok::<_, PathResolveError>( self.descriptor_parser.parse_path_query(path)?.into_read(), ) }) @@ -2642,7 +2862,7 @@ impl PermissionsContainer { pub fn query_write( &self, path: Option<&str>, - ) -> Result<PermissionState, AnyError> { + ) -> Result<PermissionState, PathResolveError> { let inner = self.inner.lock(); let permission = &inner.write; if permission.is_allow_all() { @@ -2652,7 +2872,7 @@ impl PermissionsContainer { permission.query( path .map(|path| { - Result::<_, AnyError>::Ok( + Ok::<_, PathResolveError>( self.descriptor_parser.parse_path_query(path)?.into_write(), ) }) @@ -2666,7 +2886,7 @@ impl PermissionsContainer { pub fn query_net( &self, host: Option<&str>, - ) -> Result<PermissionState, AnyError> { + ) -> Result<PermissionState, NetDescriptorParseError> { let inner = self.inner.lock(); let permission = &inner.net; if permission.is_allow_all() { @@ -2697,7 +2917,7 @@ impl PermissionsContainer { pub fn query_sys( &self, kind: Option<&str>, - ) -> Result<PermissionState, AnyError> { + ) -> Result<PermissionState, SysDescriptorParseError> { let inner = self.inner.lock(); let permission = &inner.sys; if permission.is_allow_all() { @@ -2717,7 +2937,7 @@ impl PermissionsContainer { pub fn query_run( &self, cmd: Option<&str>, - ) -> Result<PermissionState, AnyError> { + ) -> Result<PermissionState, RunDescriptorParseError> { let inner = self.inner.lock(); let permission = &inner.run; if permission.is_allow_all() { @@ -2737,7 +2957,7 @@ impl PermissionsContainer { pub fn query_ffi( &self, path: Option<&str>, - ) -> Result<PermissionState, AnyError> { + ) -> Result<PermissionState, PathResolveError> { let inner = self.inner.lock(); let permission = &inner.ffi; if permission.is_allow_all() { @@ -2747,7 +2967,7 @@ impl PermissionsContainer { permission.query( path .map(|path| { - Result::<_, AnyError>::Ok( + Ok::<_, PathResolveError>( self.descriptor_parser.parse_path_query(path)?.into_ffi(), ) }) @@ -2763,12 +2983,12 @@ impl PermissionsContainer { pub fn revoke_read( &self, path: Option<&str>, - ) -> Result<PermissionState, AnyError> { + ) -> Result<PermissionState, PathResolveError> { Ok( self.inner.lock().read.revoke( path .map(|path| { - Result::<_, AnyError>::Ok( + Ok::<_, PathResolveError>( self.descriptor_parser.parse_path_query(path)?.into_read(), ) }) @@ -2782,12 +3002,12 @@ impl PermissionsContainer { pub fn revoke_write( &self, path: Option<&str>, - ) -> Result<PermissionState, AnyError> { + ) -> Result<PermissionState, PathResolveError> { Ok( self.inner.lock().write.revoke( path .map(|path| { - Result::<_, AnyError>::Ok( + Ok::<_, PathResolveError>( self.descriptor_parser.parse_path_query(path)?.into_write(), ) }) @@ -2801,7 +3021,7 @@ impl PermissionsContainer { pub fn revoke_net( &self, host: Option<&str>, - ) -> Result<PermissionState, AnyError> { + ) -> Result<PermissionState, NetDescriptorParseError> { Ok( self.inner.lock().net.revoke( match host { @@ -2822,7 +3042,7 @@ impl PermissionsContainer { pub fn revoke_sys( &self, kind: Option<&str>, - ) -> Result<PermissionState, AnyError> { + ) -> Result<PermissionState, SysDescriptorParseError> { Ok( self.inner.lock().sys.revoke( kind @@ -2837,7 +3057,7 @@ impl PermissionsContainer { pub fn revoke_run( &self, cmd: Option<&str>, - ) -> Result<PermissionState, AnyError> { + ) -> Result<PermissionState, RunDescriptorParseError> { Ok( self.inner.lock().run.revoke( cmd @@ -2852,12 +3072,12 @@ impl PermissionsContainer { pub fn revoke_ffi( &self, path: Option<&str>, - ) -> Result<PermissionState, AnyError> { + ) -> Result<PermissionState, PathResolveError> { Ok( self.inner.lock().ffi.revoke( path .map(|path| { - Result::<_, AnyError>::Ok( + Ok::<_, PathResolveError>( self.descriptor_parser.parse_path_query(path)?.into_ffi(), ) }) @@ -2873,12 +3093,12 @@ impl PermissionsContainer { pub fn request_read( &self, path: Option<&str>, - ) -> Result<PermissionState, AnyError> { + ) -> Result<PermissionState, PathResolveError> { Ok( self.inner.lock().read.request( path .map(|path| { - Result::<_, AnyError>::Ok( + Ok::<_, PathResolveError>( self.descriptor_parser.parse_path_query(path)?.into_read(), ) }) @@ -2892,12 +3112,12 @@ impl PermissionsContainer { pub fn request_write( &self, path: Option<&str>, - ) -> Result<PermissionState, AnyError> { + ) -> Result<PermissionState, PathResolveError> { Ok( self.inner.lock().write.request( path .map(|path| { - Result::<_, AnyError>::Ok( + Ok::<_, PathResolveError>( self.descriptor_parser.parse_path_query(path)?.into_write(), ) }) @@ -2911,7 +3131,7 @@ impl PermissionsContainer { pub fn request_net( &self, host: Option<&str>, - ) -> Result<PermissionState, AnyError> { + ) -> Result<PermissionState, NetDescriptorParseError> { Ok( self.inner.lock().net.request( match host { @@ -2932,7 +3152,7 @@ impl PermissionsContainer { pub fn request_sys( &self, kind: Option<&str>, - ) -> Result<PermissionState, AnyError> { + ) -> Result<PermissionState, SysDescriptorParseError> { Ok( self.inner.lock().sys.request( kind @@ -2947,7 +3167,7 @@ impl PermissionsContainer { pub fn request_run( &self, cmd: Option<&str>, - ) -> Result<PermissionState, AnyError> { + ) -> Result<PermissionState, RunDescriptorParseError> { Ok( self.inner.lock().run.request( cmd @@ -2962,12 +3182,12 @@ impl PermissionsContainer { pub fn request_ffi( &self, path: Option<&str>, - ) -> Result<PermissionState, AnyError> { + ) -> Result<PermissionState, PathResolveError> { Ok( self.inner.lock().ffi.request( path .map(|path| { - Result::<_, AnyError>::Ok( + Ok::<_, PathResolveError>( self.descriptor_parser.parse_path_query(path)?.into_ffi(), ) }) @@ -3003,10 +3223,6 @@ fn global_from_option<T>(flag: Option<&HashSet<T>>) -> bool { matches!(flag, Some(v) if v.is_empty()) } -fn escalation_error() -> AnyError { - custom_error("NotCapable", "Can't escalate parent thread permissions") -} - #[derive(Debug, Eq, PartialEq)] pub enum ChildUnitPermissionArg { Inherit, @@ -3267,65 +3483,73 @@ pub trait PermissionDescriptorParser: Debug + Send + Sync { fn parse_read_descriptor( &self, text: &str, - ) -> Result<ReadDescriptor, AnyError>; + ) -> Result<ReadDescriptor, PathResolveError>; fn parse_write_descriptor( &self, text: &str, - ) -> Result<WriteDescriptor, AnyError>; + ) -> Result<WriteDescriptor, PathResolveError>; - fn parse_net_descriptor(&self, text: &str) - -> Result<NetDescriptor, AnyError>; + fn parse_net_descriptor( + &self, + text: &str, + ) -> Result<NetDescriptor, NetDescriptorParseError>; fn parse_net_descriptor_from_url( &self, url: &Url, - ) -> Result<NetDescriptor, AnyError> { + ) -> Result<NetDescriptor, NetDescriptorFromUrlParseError> { NetDescriptor::from_url(url) } fn parse_import_descriptor( &self, text: &str, - ) -> Result<ImportDescriptor, AnyError>; + ) -> Result<ImportDescriptor, NetDescriptorParseError>; fn parse_import_descriptor_from_url( &self, url: &Url, - ) -> Result<ImportDescriptor, AnyError> { + ) -> Result<ImportDescriptor, NetDescriptorFromUrlParseError> { ImportDescriptor::from_url(url) } - fn parse_env_descriptor(&self, text: &str) - -> Result<EnvDescriptor, AnyError>; + fn parse_env_descriptor( + &self, + text: &str, + ) -> Result<EnvDescriptor, EnvDescriptorParseError>; - fn parse_sys_descriptor(&self, text: &str) - -> Result<SysDescriptor, AnyError>; + fn parse_sys_descriptor( + &self, + text: &str, + ) -> Result<SysDescriptor, SysDescriptorParseError>; fn parse_allow_run_descriptor( &self, text: &str, - ) -> Result<AllowRunDescriptorParseResult, AnyError>; + ) -> Result<AllowRunDescriptorParseResult, RunDescriptorParseError>; fn parse_deny_run_descriptor( &self, text: &str, - ) -> Result<DenyRunDescriptor, AnyError>; + ) -> Result<DenyRunDescriptor, PathResolveError>; - fn parse_ffi_descriptor(&self, text: &str) - -> Result<FfiDescriptor, AnyError>; + fn parse_ffi_descriptor( + &self, + text: &str, + ) -> Result<FfiDescriptor, PathResolveError>; // queries fn parse_path_query( &self, path: &str, - ) -> Result<PathQueryDescriptor, AnyError>; + ) -> Result<PathQueryDescriptor, PathResolveError>; fn parse_run_query( &self, requested: &str, - ) -> Result<RunQueryDescriptor, AnyError>; + ) -> Result<RunQueryDescriptor, RunDescriptorParseError>; } static IS_STANDALONE: AtomicFlag = AtomicFlag::lowered(); @@ -3368,49 +3592,49 @@ mod tests { fn parse_read_descriptor( &self, text: &str, - ) -> Result<ReadDescriptor, AnyError> { + ) -> Result<ReadDescriptor, PathResolveError> { Ok(ReadDescriptor(self.join_path_with_root(text))) } fn parse_write_descriptor( &self, text: &str, - ) -> Result<WriteDescriptor, AnyError> { + ) -> Result<WriteDescriptor, PathResolveError> { Ok(WriteDescriptor(self.join_path_with_root(text))) } fn parse_net_descriptor( &self, text: &str, - ) -> Result<NetDescriptor, AnyError> { + ) -> Result<NetDescriptor, NetDescriptorParseError> { NetDescriptor::parse(text) } fn parse_import_descriptor( &self, text: &str, - ) -> Result<ImportDescriptor, AnyError> { + ) -> Result<ImportDescriptor, NetDescriptorParseError> { ImportDescriptor::parse(text) } fn parse_env_descriptor( &self, text: &str, - ) -> Result<EnvDescriptor, AnyError> { + ) -> Result<EnvDescriptor, EnvDescriptorParseError> { Ok(EnvDescriptor::new(text)) } fn parse_sys_descriptor( &self, text: &str, - ) -> Result<SysDescriptor, AnyError> { + ) -> Result<SysDescriptor, SysDescriptorParseError> { SysDescriptor::parse(text.to_string()) } fn parse_allow_run_descriptor( &self, text: &str, - ) -> Result<AllowRunDescriptorParseResult, AnyError> { + ) -> Result<AllowRunDescriptorParseResult, RunDescriptorParseError> { Ok(AllowRunDescriptorParseResult::Descriptor( AllowRunDescriptor(self.join_path_with_root(text)), )) @@ -3419,7 +3643,7 @@ mod tests { fn parse_deny_run_descriptor( &self, text: &str, - ) -> Result<DenyRunDescriptor, AnyError> { + ) -> Result<DenyRunDescriptor, PathResolveError> { if text.contains("/") { Ok(DenyRunDescriptor::Path(self.join_path_with_root(text))) } else { @@ -3430,14 +3654,14 @@ mod tests { fn parse_ffi_descriptor( &self, text: &str, - ) -> Result<FfiDescriptor, AnyError> { + ) -> Result<FfiDescriptor, PathResolveError> { Ok(FfiDescriptor(self.join_path_with_root(text))) } fn parse_path_query( &self, path: &str, - ) -> Result<PathQueryDescriptor, AnyError> { + ) -> Result<PathQueryDescriptor, PathResolveError> { Ok(PathQueryDescriptor { resolved: self.join_path_with_root(path), requested: path.to_string(), @@ -3447,8 +3671,8 @@ mod tests { fn parse_run_query( &self, requested: &str, - ) -> Result<RunQueryDescriptor, AnyError> { - RunQueryDescriptor::parse(requested) + ) -> Result<RunQueryDescriptor, RunDescriptorParseError> { + RunQueryDescriptor::parse(requested).map_err(Into::into) } } @@ -4329,7 +4553,6 @@ mod tests { None, false, ) - .unwrap() }; prompt_value.set(true); @@ -4556,13 +4779,12 @@ mod tests { .lock() .clone(), Permissions { - env: Permissions::new_unary(Some(HashSet::new()), None, false).unwrap(), + env: Permissions::new_unary(Some(HashSet::new()), None, false), net: Permissions::new_unary( Some(HashSet::from([NetDescriptor::parse("foo").unwrap()])), None, false - ) - .unwrap(), + ), ..Permissions::none_without_prompt() } ); diff --git a/runtime/permissions/prompter.rs b/runtime/permissions/prompter.rs index e48e0af10..168a845a2 100644 --- a/runtime/permissions/prompter.rs +++ b/runtime/permissions/prompter.rs @@ -1,6 +1,5 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -use deno_core::error::AnyError; use deno_core::parking_lot::Mutex; use deno_terminal::colors; use once_cell::sync::Lazy; @@ -80,6 +79,10 @@ pub fn set_prompt_callbacks( *MAYBE_AFTER_PROMPT_CALLBACK.lock() = Some(after_callback); } +pub fn set_prompter(prompter: Box<dyn PermissionPrompter>) { + *PERMISSION_PROMPTER.lock() = prompter; +} + pub type PromptCallback = Box<dyn FnMut() + Send + Sync>; pub trait PermissionPrompter: Send + Sync { @@ -97,8 +100,7 @@ pub struct TtyPrompter; fn clear_stdin( _stdin_lock: &mut StdinLock, _stderr_lock: &mut StderrLock, -) -> Result<(), AnyError> { - use deno_core::anyhow::bail; +) -> Result<(), std::io::Error> { use std::mem::MaybeUninit; const STDIN_FD: i32 = 0; @@ -113,7 +115,10 @@ fn clear_stdin( loop { let r = libc::tcflush(STDIN_FD, libc::TCIFLUSH); if r != 0 { - bail!("clear_stdin failed (tcflush)"); + return Err(std::io::Error::new( + std::io::ErrorKind::Other, + "clear_stdin failed (tcflush)", + )); } // Initialize timeout for select to be 100ms @@ -133,7 +138,10 @@ fn clear_stdin( // Check if select returned an error if r < 0 { - bail!("clear_stdin failed (select)"); + return Err(std::io::Error::new( + std::io::ErrorKind::Other, + "clear_stdin failed (select)", + )); } // Check if select returned due to timeout (stdin is quiescent) @@ -152,8 +160,7 @@ fn clear_stdin( fn clear_stdin( stdin_lock: &mut StdinLock, stderr_lock: &mut StderrLock, -) -> Result<(), AnyError> { - use deno_core::anyhow::bail; +) -> Result<(), std::io::Error> { use winapi::shared::minwindef::TRUE; use winapi::shared::minwindef::UINT; use winapi::shared::minwindef::WORD; @@ -190,18 +197,23 @@ fn clear_stdin( return Ok(()); - unsafe fn flush_input_buffer(stdin: HANDLE) -> Result<(), AnyError> { + unsafe fn flush_input_buffer(stdin: HANDLE) -> Result<(), std::io::Error> { let success = FlushConsoleInputBuffer(stdin); if success != TRUE { - bail!( - "Could not flush the console input buffer: {}", - std::io::Error::last_os_error() - ) + return Err(std::io::Error::new( + std::io::ErrorKind::Other, + format!( + "Could not flush the console input buffer: {}", + std::io::Error::last_os_error() + ), + )); } Ok(()) } - unsafe fn emulate_enter_key_press(stdin: HANDLE) -> Result<(), AnyError> { + unsafe fn emulate_enter_key_press( + stdin: HANDLE, + ) -> Result<(), std::io::Error> { // https://github.com/libuv/libuv/blob/a39009a5a9252a566ca0704d02df8dabc4ce328f/src/win/tty.c#L1121-L1131 let mut input_record: INPUT_RECORD = std::mem::zeroed(); input_record.EventType = KEY_EVENT; @@ -216,34 +228,43 @@ fn clear_stdin( let success = WriteConsoleInputW(stdin, &input_record, 1, &mut record_written); if success != TRUE { - bail!( - "Could not emulate enter key press: {}", - std::io::Error::last_os_error() - ); + return Err(std::io::Error::new( + std::io::ErrorKind::Other, + format!( + "Could not emulate enter key press: {}", + std::io::Error::last_os_error() + ), + )); } Ok(()) } - unsafe fn is_input_buffer_empty(stdin: HANDLE) -> Result<bool, AnyError> { + unsafe fn is_input_buffer_empty( + stdin: HANDLE, + ) -> Result<bool, std::io::Error> { let mut buffer = Vec::with_capacity(1); let mut events_read = 0; let success = PeekConsoleInputW(stdin, buffer.as_mut_ptr(), 1, &mut events_read); if success != TRUE { - bail!( - "Could not peek the console input buffer: {}", - std::io::Error::last_os_error() - ) + return Err(std::io::Error::new( + std::io::ErrorKind::Other, + format!( + "Could not peek the console input buffer: {}", + std::io::Error::last_os_error() + ), + )); } Ok(events_read == 0) } - fn move_cursor_up(stderr_lock: &mut StderrLock) -> Result<(), AnyError> { - write!(stderr_lock, "\x1B[1A")?; - Ok(()) + fn move_cursor_up( + stderr_lock: &mut StderrLock, + ) -> Result<(), std::io::Error> { + write!(stderr_lock, "\x1B[1A") } - fn read_stdin_line(stdin_lock: &mut StdinLock) -> Result<(), AnyError> { + fn read_stdin_line(stdin_lock: &mut StdinLock) -> Result<(), std::io::Error> { let mut input = String::new(); stdin_lock.read_line(&mut input)?; Ok(()) @@ -265,7 +286,7 @@ fn get_stdin_metadata() -> std::io::Result<std::fs::Metadata> { unsafe { let stdin = std::fs::File::from_raw_fd(0); let metadata = stdin.metadata().unwrap(); - stdin.into_raw_fd(); + let _ = stdin.into_raw_fd(); Ok(metadata) } } @@ -366,7 +387,7 @@ impl PermissionPrompter for TtyPrompter { let mut input = String::new(); let result = stdin_lock.read_line(&mut input); - let input = input.trim_end_matches(|c| c == '\r' || c == '\n'); + let input = input.trim_end_matches(['\r', '\n']); if result.is_err() || input.len() != 1 { break PromptResponse::Deny; }; @@ -476,8 +497,4 @@ pub mod tests { STUB_PROMPT_VALUE.store(value, Ordering::SeqCst); } } - - pub fn set_prompter(prompter: Box<dyn PermissionPrompter>) { - *PERMISSION_PROMPTER.lock() = prompter; - } } diff --git a/runtime/shared.rs b/runtime/shared.rs index 02dfd1871..b1f383b03 100644 --- a/runtime/shared.rs +++ b/runtime/shared.rs @@ -47,6 +47,7 @@ extension!(runtime, "40_signals.js", "40_tty.js", "41_prompt.js", + "telemetry.ts", "90_deno_ns.js", "98_global_scope_shared.js", "98_global_scope_window.js", @@ -98,6 +99,7 @@ pub fn maybe_transpile_source( imports_not_used_as_values: deno_ast::ImportsNotUsedAsValues::Remove, ..Default::default() }, + &deno_ast::TranspileModuleOptions::default(), &deno_ast::EmitOptions { source_map: if cfg!(debug_assertions) { SourceMapOption::Separate @@ -109,9 +111,9 @@ pub fn maybe_transpile_source( )? .into_source(); - let maybe_source_map: Option<SourceMapData> = - transpiled_source.source_map.map(|sm| sm.into()); - let source_text = String::from_utf8(transpiled_source.source)?; - + let maybe_source_map: Option<SourceMapData> = transpiled_source + .source_map + .map(|sm| sm.into_bytes().into()); + let source_text = transpiled_source.text; Ok((source_text.into(), maybe_source_map)) } diff --git a/runtime/snapshot.rs b/runtime/snapshot.rs index 041132f97..3bf515131 100644 --- a/runtime/snapshot.rs +++ b/runtime/snapshot.rs @@ -5,12 +5,12 @@ use crate::ops::bootstrap::SnapshotOptions; use crate::shared::maybe_transpile_source; use crate::shared::runtime; use deno_cache::SqliteBackedCache; -use deno_core::error::AnyError; use deno_core::snapshot::*; use deno_core::v8; use deno_core::Extension; use deno_http::DefaultHttpPropertyExtractor; use deno_io::fs::FsError; +use deno_permissions::PermissionCheckError; use std::borrow::Cow; use std::io::Write; use std::path::Path; @@ -26,7 +26,7 @@ impl deno_websocket::WebSocketPermissions for Permissions { &mut self, _url: &deno_core::url::Url, _api_name: &str, - ) -> Result<(), deno_core::error::AnyError> { + ) -> Result<(), PermissionCheckError> { unreachable!("snapshotting!") } } @@ -42,7 +42,7 @@ impl deno_fetch::FetchPermissions for Permissions { &mut self, _url: &deno_core::url::Url, _api_name: &str, - ) -> Result<(), deno_core::error::AnyError> { + ) -> Result<(), PermissionCheckError> { unreachable!("snapshotting!") } @@ -50,28 +50,26 @@ impl deno_fetch::FetchPermissions for Permissions { &mut self, _p: &'a Path, _api_name: &str, - ) -> Result<Cow<'a, Path>, AnyError> { + ) -> Result<Cow<'a, Path>, PermissionCheckError> { unreachable!("snapshotting!") } } impl deno_ffi::FfiPermissions for Permissions { - fn check_partial_no_path( - &mut self, - ) -> Result<(), deno_core::error::AnyError> { + fn check_partial_no_path(&mut self) -> Result<(), PermissionCheckError> { unreachable!("snapshotting!") } fn check_partial_with_path( &mut self, _path: &str, - ) -> Result<PathBuf, AnyError> { + ) -> Result<PathBuf, PermissionCheckError> { unreachable!("snapshotting!") } } impl deno_napi::NapiPermissions for Permissions { - fn check(&mut self, _path: &str) -> std::result::Result<PathBuf, AnyError> { + fn check(&mut self, _path: &str) -> Result<PathBuf, PermissionCheckError> { unreachable!("snapshotting!") } } @@ -81,20 +79,27 @@ impl deno_node::NodePermissions for Permissions { &mut self, _url: &deno_core::url::Url, _api_name: &str, - ) -> Result<(), deno_core::error::AnyError> { + ) -> Result<(), PermissionCheckError> { + unreachable!("snapshotting!") + } + fn check_net( + &mut self, + _host: (&str, Option<u16>), + _api_name: &str, + ) -> Result<(), PermissionCheckError> { unreachable!("snapshotting!") } fn check_read_path<'a>( &mut self, _path: &'a Path, - ) -> Result<Cow<'a, Path>, AnyError> { + ) -> Result<Cow<'a, Path>, PermissionCheckError> { unreachable!("snapshotting!") } fn check_read_with_api_name( &mut self, _p: &str, _api_name: Option<&str>, - ) -> Result<PathBuf, deno_core::error::AnyError> { + ) -> Result<PathBuf, PermissionCheckError> { unreachable!("snapshotting!") } fn query_read_all(&mut self) -> bool { @@ -104,14 +109,14 @@ impl deno_node::NodePermissions for Permissions { &mut self, _p: &str, _api_name: Option<&str>, - ) -> Result<PathBuf, deno_core::error::AnyError> { + ) -> Result<PathBuf, PermissionCheckError> { unreachable!("snapshotting!") } fn check_sys( &mut self, _kind: &str, _api_name: &str, - ) -> Result<(), deno_core::error::AnyError> { + ) -> Result<(), PermissionCheckError> { unreachable!("snapshotting!") } } @@ -121,7 +126,7 @@ impl deno_net::NetPermissions for Permissions { &mut self, _host: &(T, Option<u16>), _api_name: &str, - ) -> Result<(), deno_core::error::AnyError> { + ) -> Result<(), PermissionCheckError> { unreachable!("snapshotting!") } @@ -129,7 +134,7 @@ impl deno_net::NetPermissions for Permissions { &mut self, _p: &str, _api_name: &str, - ) -> Result<PathBuf, deno_core::error::AnyError> { + ) -> Result<PathBuf, PermissionCheckError> { unreachable!("snapshotting!") } @@ -137,7 +142,7 @@ impl deno_net::NetPermissions for Permissions { &mut self, _p: &str, _api_name: &str, - ) -> Result<PathBuf, deno_core::error::AnyError> { + ) -> Result<PathBuf, PermissionCheckError> { unreachable!("snapshotting!") } @@ -145,7 +150,7 @@ impl deno_net::NetPermissions for Permissions { &mut self, _p: &'a Path, _api_name: &str, - ) -> Result<std::borrow::Cow<'a, Path>, AnyError> { + ) -> Result<Cow<'a, Path>, PermissionCheckError> { unreachable!("snapshotting!") } } @@ -158,7 +163,7 @@ impl deno_fs::FsPermissions for Permissions { _write: bool, _path: &'a Path, _api_name: &str, - ) -> Result<std::borrow::Cow<'a, Path>, FsError> { + ) -> Result<Cow<'a, Path>, FsError> { unreachable!("snapshotting!") } @@ -166,11 +171,14 @@ impl deno_fs::FsPermissions for Permissions { &mut self, _path: &str, _api_name: &str, - ) -> Result<PathBuf, AnyError> { + ) -> Result<PathBuf, PermissionCheckError> { unreachable!("snapshotting!") } - fn check_read_all(&mut self, _api_name: &str) -> Result<(), AnyError> { + fn check_read_all( + &mut self, + _api_name: &str, + ) -> Result<(), PermissionCheckError> { unreachable!("snapshotting!") } @@ -179,7 +187,7 @@ impl deno_fs::FsPermissions for Permissions { _path: &Path, _display: &str, _api_name: &str, - ) -> Result<(), AnyError> { + ) -> Result<(), PermissionCheckError> { unreachable!("snapshotting!") } @@ -187,7 +195,7 @@ impl deno_fs::FsPermissions for Permissions { &mut self, _path: &str, _api_name: &str, - ) -> Result<PathBuf, AnyError> { + ) -> Result<PathBuf, PermissionCheckError> { unreachable!("snapshotting!") } @@ -195,11 +203,14 @@ impl deno_fs::FsPermissions for Permissions { &mut self, _path: &str, _api_name: &str, - ) -> Result<PathBuf, AnyError> { + ) -> Result<PathBuf, PermissionCheckError> { unreachable!("snapshotting!") } - fn check_write_all(&mut self, _api_name: &str) -> Result<(), AnyError> { + fn check_write_all( + &mut self, + _api_name: &str, + ) -> Result<(), PermissionCheckError> { unreachable!("snapshotting!") } @@ -208,7 +219,7 @@ impl deno_fs::FsPermissions for Permissions { _path: &Path, _display: &str, _api_name: &str, - ) -> Result<(), AnyError> { + ) -> Result<(), PermissionCheckError> { unreachable!("snapshotting!") } @@ -216,7 +227,7 @@ impl deno_fs::FsPermissions for Permissions { &mut self, _path: &'a Path, _api_name: &str, - ) -> Result<std::borrow::Cow<'a, Path>, AnyError> { + ) -> Result<Cow<'a, Path>, PermissionCheckError> { unreachable!("snapshotting!") } @@ -224,7 +235,7 @@ impl deno_fs::FsPermissions for Permissions { &mut self, _path: &'a Path, _api_name: &str, - ) -> Result<std::borrow::Cow<'a, Path>, AnyError> { + ) -> Result<Cow<'a, Path>, PermissionCheckError> { unreachable!("snapshotting!") } } @@ -234,7 +245,7 @@ impl deno_kv::sqlite::SqliteDbHandlerPermissions for Permissions { &mut self, _path: &str, _api_name: &str, - ) -> Result<PathBuf, AnyError> { + ) -> Result<PathBuf, PermissionCheckError> { unreachable!("snapshotting!") } @@ -242,7 +253,7 @@ impl deno_kv::sqlite::SqliteDbHandlerPermissions for Permissions { &mut self, _path: &'a Path, _api_name: &str, - ) -> Result<Cow<'a, Path>, AnyError> { + ) -> Result<Cow<'a, Path>, PermissionCheckError> { unreachable!("snapshotting!") } } @@ -289,7 +300,9 @@ pub fn create_runtime_snapshot( deno_cron::local::LocalCronHandler::new(), ), deno_napi::deno_napi::init_ops_and_esm::<Permissions>(), - deno_http::deno_http::init_ops_and_esm::<DefaultHttpPropertyExtractor>(), + deno_http::deno_http::init_ops_and_esm::<DefaultHttpPropertyExtractor>( + deno_http::Options::default(), + ), deno_io::deno_io::init_ops_and_esm(Default::default()), deno_fs::deno_fs::init_ops_and_esm::<Permissions>(fs.clone()), deno_node::deno_node::init_ops_and_esm::<Permissions>(None, fs.clone()), @@ -301,6 +314,7 @@ pub fn create_runtime_snapshot( ), ops::fs_events::deno_fs_events::init_ops(), ops::os::deno_os::init_ops(Default::default()), + ops::otel::deno_otel::init_ops(), ops::permissions::deno_permissions::init_ops(), ops::process::deno_process::init_ops(None), ops::signal::deno_signal::init_ops(), diff --git a/runtime/ops/os/sys_info.rs b/runtime/sys_info.rs index cffc90e9d..cffc90e9d 100644 --- a/runtime/ops/os/sys_info.rs +++ b/runtime/sys_info.rs diff --git a/runtime/tokio_util.rs b/runtime/tokio_util.rs index 0d81f6e23..aa0282ece 100644 --- a/runtime/tokio_util.rs +++ b/runtime/tokio_util.rs @@ -43,7 +43,15 @@ pub fn create_basic_runtime() -> tokio::runtime::Runtime { // parallel for deno fmt. // The default value is 512, which is an unhelpfully large thread pool. We // don't ever want to have more than a couple dozen threads. - .max_blocking_threads(32) + .max_blocking_threads(if cfg!(windows) { + // on windows, tokio uses blocking tasks for child process IO, make sure + // we have enough available threads for other tasks to run + 4 * std::thread::available_parallelism() + .map(|n| n.get()) + .unwrap_or(8) + } else { + 32 + }) .build() .unwrap() } diff --git a/runtime/web_worker.rs b/runtime/web_worker.rs index f560ce17e..a282d11b9 100644 --- a/runtime/web_worker.rs +++ b/runtime/web_worker.rs @@ -166,7 +166,10 @@ pub struct WebWorkerInternalHandle { impl WebWorkerInternalHandle { /// Post WorkerEvent to parent as a worker - pub fn post_event(&self, event: WorkerControlEvent) -> Result<(), AnyError> { + pub fn post_event( + &self, + event: WorkerControlEvent, + ) -> Result<(), mpsc::TrySendError<WorkerControlEvent>> { let mut sender = self.sender.clone(); // If the channel is closed, // the worker must have terminated but the termination message has not yet been received. @@ -176,8 +179,7 @@ impl WebWorkerInternalHandle { self.has_terminated.store(true, Ordering::SeqCst); return Ok(()); } - sender.try_send(event)?; - Ok(()) + sender.try_send(event) } /// Check if this worker is terminated or being terminated @@ -263,11 +265,9 @@ impl WebWorkerHandle { /// Get the WorkerEvent with lock /// Return error if more than one listener tries to get event #[allow(clippy::await_holding_refcell_ref)] // TODO(ry) remove! - pub async fn get_control_event( - &self, - ) -> Result<Option<WorkerControlEvent>, AnyError> { + pub async fn get_control_event(&self) -> Option<WorkerControlEvent> { let mut receiver = self.receiver.borrow_mut(); - Ok(receiver.next().await) + receiver.next().await } /// Terminate the worker @@ -361,6 +361,8 @@ pub struct WebWorkerOptions { pub extensions: Vec<Extension>, pub startup_snapshot: Option<&'static [u8]>, pub unsafely_ignore_certificate_errors: Option<Vec<String>>, + /// Optional isolate creation parameters, such as heap limits. + pub create_params: Option<v8::CreateParams>, pub seed: Option<u64>, pub create_web_worker_cb: Arc<ops::worker_host::CreateWebWorkerCb>, pub format_js_error_fn: Option<Arc<FormatJsErrorFn>>, @@ -393,6 +395,13 @@ pub struct WebWorker { maybe_worker_metadata: Option<WorkerMetadata>, } +impl Drop for WebWorker { + fn drop(&mut self) { + // clean up the package.json thread local cache + node_resolver::PackageJsonThreadLocalCache::clear(); + } +} + impl WebWorker { pub fn bootstrap_from_options( services: WebWorkerServiceOptions, @@ -488,7 +497,9 @@ impl WebWorker { ), deno_cron::deno_cron::init_ops_and_esm(LocalCronHandler::new()), deno_napi::deno_napi::init_ops_and_esm::<PermissionsContainer>(), - deno_http::deno_http::init_ops_and_esm::<DefaultHttpPropertyExtractor>(), + deno_http::deno_http::init_ops_and_esm::<DefaultHttpPropertyExtractor>( + deno_http::Options::default(), + ), deno_io::deno_io::init_ops_and_esm(Some(options.stdio)), deno_fs::deno_fs::init_ops_and_esm::<PermissionsContainer>( services.fs.clone(), @@ -505,6 +516,7 @@ impl WebWorker { ), ops::fs_events::deno_fs_events::init_ops_and_esm(), ops::os::deno_os_worker::init_ops_and_esm(), + ops::otel::deno_otel::init_ops_and_esm(), ops::permissions::deno_permissions::init_ops_and_esm(), ops::process::deno_process::init_ops_and_esm( services.npm_process_state_provider, @@ -555,6 +567,7 @@ impl WebWorker { let mut js_runtime = JsRuntime::new(RuntimeOptions { module_loader: Some(services.module_loader), startup_snapshot: options.startup_snapshot, + create_params: options.create_params, get_error_class_fn: options.get_error_class_fn, shared_array_buffer_store: services.shared_array_buffer_store, compiled_wasm_module_store: services.compiled_wasm_module_store, @@ -562,7 +575,7 @@ impl WebWorker { extension_transpiler: Some(Rc::new(|specifier, source| { maybe_transpile_source(specifier, source) })), - inspector: services.maybe_inspector_server.is_some(), + inspector: true, feature_checker: Some(services.feature_checker), op_metrics_factory_fn, import_meta_resolve_callback: Some(Box::new( @@ -579,18 +592,18 @@ impl WebWorker { js_runtime.op_state().borrow_mut().put(op_summary_metrics); } + // Put inspector handle into the op state so we can put a breakpoint when + // executing a CJS entrypoint. + let op_state = js_runtime.op_state(); + let inspector = js_runtime.inspector(); + op_state.borrow_mut().put(inspector); + if let Some(server) = services.maybe_inspector_server { server.register_inspector( options.main_module.to_string(), &mut js_runtime, false, ); - - // Put inspector handle into the op state so we can put a breakpoint when - // executing a CJS entrypoint. - let op_state = js_runtime.op_state(); - let inspector = js_runtime.inspector(); - op_state.borrow_mut().put(inspector); } let (internal_handle, external_handle) = { @@ -821,13 +834,12 @@ impl WebWorker { // TODO(mmastrac): we don't want to test this w/classic workers because // WPT triggers a failure here. This is only exposed via --enable-testing-features-do-not-use. - #[allow(clippy::print_stderr)] if self.worker_type == WebWorkerType::Module { panic!( "coding error: either js is polling or the worker is terminated" ); } else { - eprintln!("classic worker terminated unexpectedly"); + log::error!("classic worker terminated unexpectedly"); Poll::Ready(Ok(())) } } @@ -895,7 +907,6 @@ impl WebWorker { } } -#[allow(clippy::print_stderr)] fn print_worker_error( error: &AnyError, name: &str, @@ -908,7 +919,7 @@ fn print_worker_error( }, None => error.to_string(), }; - eprintln!( + log::error!( "{}: Uncaught (in worker \"{}\") {}", colors::red_bold("error"), name, diff --git a/runtime/worker.rs b/runtime/worker.rs index 477d3b880..909147df9 100644 --- a/runtime/worker.rs +++ b/runtime/worker.rs @@ -20,6 +20,8 @@ use deno_core::CompiledWasmModuleStore; use deno_core::Extension; use deno_core::FeatureChecker; use deno_core::GetErrorClassFn; +use deno_core::InspectorSessionKind; +use deno_core::InspectorSessionOptions; use deno_core::JsRuntime; use deno_core::LocalInspectorSession; use deno_core::ModuleCodeString; @@ -141,6 +143,7 @@ pub struct WorkerServiceOptions { pub npm_process_state_provider: Option<NpmProcessStateProviderRc>, pub permissions: PermissionsContainer, pub root_cert_store_provider: Option<Arc<dyn RootCertStoreProvider>>, + pub fetch_dns_resolver: deno_fetch::dns::Resolver, /// The store to use for transferring SharedArrayBuffers between isolates. /// If multiple isolates should have the possibility of sharing @@ -361,6 +364,7 @@ impl MainWorker { .unsafely_ignore_certificate_errors .clone(), file_fetch_handler: Rc::new(deno_fetch::FsFetchHandler), + resolver: services.fetch_dns_resolver, ..Default::default() }, ), @@ -403,7 +407,9 @@ impl MainWorker { ), deno_cron::deno_cron::init_ops_and_esm(LocalCronHandler::new()), deno_napi::deno_napi::init_ops_and_esm::<PermissionsContainer>(), - deno_http::deno_http::init_ops_and_esm::<DefaultHttpPropertyExtractor>(), + deno_http::deno_http::init_ops_and_esm::<DefaultHttpPropertyExtractor>( + deno_http::Options::default(), + ), deno_io::deno_io::init_ops_and_esm(Some(options.stdio)), deno_fs::deno_fs::init_ops_and_esm::<PermissionsContainer>( services.fs.clone(), @@ -420,6 +426,7 @@ impl MainWorker { ), ops::fs_events::deno_fs_events::init_ops_and_esm(), ops::os::deno_os::init_ops_and_esm(exit_code.clone()), + ops::otel::deno_otel::init_ops_and_esm(), ops::permissions::deno_permissions::init_ops_and_esm(), ops::process::deno_process::init_ops_and_esm( services.npm_process_state_provider, @@ -486,7 +493,7 @@ impl MainWorker { extension_transpiler: Some(Rc::new(|specifier, source| { maybe_transpile_source(specifier, source) })), - inspector: options.maybe_inspector_server.is_some(), + inspector: true, is_main: true, feature_checker: Some(services.feature_checker.clone()), op_metrics_factory_fn, @@ -544,6 +551,12 @@ impl MainWorker { js_runtime.op_state().borrow_mut().put(op_summary_metrics); } + // Put inspector handle into the op state so we can put a breakpoint when + // executing a CJS entrypoint. + let op_state = js_runtime.op_state(); + let inspector = js_runtime.inspector(); + op_state.borrow_mut().put(inspector); + if let Some(server) = options.maybe_inspector_server.clone() { server.register_inspector( main_module.to_string(), @@ -551,13 +564,8 @@ impl MainWorker { options.should_break_on_first_statement || options.should_wait_for_inspector_session, ); - - // Put inspector handle into the op state so we can put a breakpoint when - // executing a CJS entrypoint. - let op_state = js_runtime.op_state(); - let inspector = js_runtime.inspector(); - op_state.borrow_mut().put(inspector); } + let ( bootstrap_fn_global, dispatch_load_event_fn_global, @@ -792,7 +800,11 @@ impl MainWorker { /// was not configured to create inspector. pub fn create_inspector_session(&mut self) -> LocalInspectorSession { self.js_runtime.maybe_init_inspector(); - self.js_runtime.inspector().borrow().create_local_session() + self.js_runtime.inspector().borrow().create_local_session( + InspectorSessionOptions { + kind: InspectorSessionKind::Blocking, + }, + ) } pub async fn run_event_loop( diff --git a/runtime/worker_bootstrap.rs b/runtime/worker_bootstrap.rs index 3f3c25c5e..3f5c245a0 100644 --- a/runtime/worker_bootstrap.rs +++ b/runtime/worker_bootstrap.rs @@ -1,5 +1,6 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +use crate::ops::otel::OtelConfig; use deno_core::v8; use deno_core::ModuleSpecifier; use serde::Serialize; @@ -118,6 +119,8 @@ pub struct BootstrapOptions { // Used by `deno serve` pub serve_port: Option<u16>, pub serve_host: Option<String>, + // OpenTelemetry output options. If `None`, OpenTelemetry is disabled. + pub otel_config: Option<OtelConfig>, } impl Default for BootstrapOptions { @@ -152,6 +155,7 @@ impl Default for BootstrapOptions { mode: WorkerExecutionMode::None, serve_port: Default::default(), serve_host: Default::default(), + otel_config: None, } } } @@ -193,6 +197,8 @@ struct BootstrapV8<'a>( Option<bool>, // serve worker count Option<usize>, + // OTEL config + Box<[u8]>, ); impl BootstrapOptions { @@ -219,6 +225,11 @@ impl BootstrapOptions { self.serve_host.as_deref(), serve_is_main, serve_worker_count, + if let Some(otel_config) = self.otel_config.as_ref() { + Box::new([otel_config.console as u8, otel_config.deterministic as u8]) + } else { + Box::new([]) + }, ); bootstrap.serialize(ser).unwrap() |