From 0a894cb4a90a960731952ebd31291ba268933165 Mon Sep 17 00:00:00 2001 From: Luca Casonato Date: Thu, 25 Nov 2021 18:31:52 +0100 Subject: [PATCH 1/4] fix(repl): don't panic on lone surrogates This commit fixes the pancis that occur when a user does various actions using strings containing lone surrogates in the REPL. It does this by introducing a new `LossyString` type that implements `serde::Deserialize`. This deserializer supprots deserializing JSON strings that contain lone surrogates. It replaces lone surrogates with the unicode replacement character. --- Cargo.lock | 5 +- Cargo.toml | 4 + cli/tools/coverage.rs | 29 ++-- cli/tools/repl.rs | 286 +++++++++++++++++++++-------------- core/Cargo.toml | 2 +- core/inspector.rs | 338 +++++++++++++++++++++++++++++++++++++----- core/lib.rs | 2 + 7 files changed, 501 insertions(+), 165 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9c6a22cd5d505..f4f43dc970dd0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3285,9 +3285,8 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.68" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f690853975602e1bfe1ccbf50504d67174e3bcf340f23b5ea9992e0587a52d8" +version = "1.0.72" +source = "git+https://github.com/lucacasonato/json.git?rev=5ec8141acf127d9dcbc01b2b63c6426d158ee1af#5ec8141acf127d9dcbc01b2b63c6426d158ee1af" dependencies = [ "indexmap", "itoa", diff --git a/Cargo.toml b/Cargo.toml index f9481ce2f5401..2d9556e8c6b48 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,6 +27,10 @@ members = [ ] exclude = ["test_util/std/hash/_wasm"] +# use serde_json lucacasonato/json.git@5ec8141acf127d9dcbc01b2b63c6426d158ee1af +[patch.crates-io] +serde_json = { git = "https://github.com/lucacasonato/json.git", rev = "5ec8141acf127d9dcbc01b2b63c6426d158ee1af" } + # NB: the `bench` and `release` profiles must remain EXACTLY the same. [profile.release] codegen-units = 1 diff --git a/cli/tools/coverage.rs b/cli/tools/coverage.rs index 60c87687ac568..9433f574cb74c 100644 --- a/cli/tools/coverage.rs +++ b/cli/tools/coverage.rs @@ -84,25 +84,37 @@ impl CoverageCollector { } async fn enable_debugger(&mut self) -> Result<(), AnyError> { - self.session.post_message("Debugger.enable", None).await?; + self + .session + .post_message("Debugger.enable", None::<()>) + .await?; Ok(()) } async fn enable_profiler(&mut self) -> Result<(), AnyError> { - self.session.post_message("Profiler.enable", None).await?; + self + .session + .post_message("Profiler.enable", None::<()>) + .await?; Ok(()) } async fn disable_debugger(&mut self) -> Result<(), AnyError> { - self.session.post_message("Debugger.disable", None).await?; + self + .session + .post_message("Debugger.disable", None::<()>) + .await?; Ok(()) } async fn disable_profiler(&mut self) -> Result<(), AnyError> { - self.session.post_message("Profiler.disable", None).await?; + self + .session + .post_message("Profiler.disable", None::<()>) + .await?; Ok(()) } @@ -111,13 +123,12 @@ impl CoverageCollector { &mut self, parameters: StartPreciseCoverageParameters, ) -> Result { - let parameters_value = serde_json::to_value(parameters)?; let return_value = self .session - .post_message("Profiler.startPreciseCoverage", Some(parameters_value)) + .post_message("Profiler.startPreciseCoverage", Some(parameters)) .await?; - let return_object = serde_json::from_value(return_value)?; + let return_object = serde_json::from_str(return_value.get())?; Ok(return_object) } @@ -127,10 +138,10 @@ impl CoverageCollector { ) -> Result { let return_value = self .session - .post_message("Profiler.takePreciseCoverage", None) + .post_message("Profiler.takePreciseCoverage", None::<()>) .await?; - let return_object = serde_json::from_value(return_value)?; + let return_object = serde_json::from_str(return_value.get())?; Ok(return_object) } diff --git a/cli/tools/repl.rs b/cli/tools/repl.rs index b6874f574d024..b7d6effeeec07 100644 --- a/cli/tools/repl.rs +++ b/cli/tools/repl.rs @@ -9,9 +9,12 @@ use deno_ast::swc::parser::token::{Token, Word}; use deno_core::error::AnyError; use deno_core::futures::FutureExt; use deno_core::parking_lot::Mutex; +use deno_core::serde_json; use deno_core::serde_json::json; +use deno_core::serde_json::value::RawValue; use deno_core::serde_json::Value; use deno_core::LocalInspectorSession; +use deno_core::LossyString; use deno_runtime::worker::MainWorker; use rustyline::completion::Completer; use rustyline::error::ReadlineError; @@ -24,6 +27,8 @@ use rustyline::Config; use rustyline::Context; use rustyline::Editor; use rustyline_derive::{Helper, Hinter}; +use serde::Deserialize; +use serde::Serialize; use std::borrow::Cow; use std::cell::RefCell; use std::path::PathBuf; @@ -35,18 +40,63 @@ use tokio::sync::mpsc::Sender; use tokio::sync::mpsc::UnboundedReceiver; use tokio::sync::mpsc::UnboundedSender; +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct RemoteObject { + r#type: String, + value: Option>, + unserializable_value: Option>, + object_id: Option, +} + +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +struct CallArgument { + #[serde(skip_serializing_if = "Option::is_none")] + value: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + unserializable_value: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + object_id: Option, +} + +impl From for CallArgument { + fn from(remote_object: RemoteObject) -> Self { + CallArgument { + value: remote_object.value, + unserializable_value: remote_object.unserializable_value, + object_id: remote_object.object_id, + } + } +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct EvaluationResponse { + exception_details: Option, + result: Option, +} + +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +struct CallFunctionRequest<'a> { + execution_context_id: u64, + function_declaration: &'static str, + arguments: &'a [&'a CallArgument], +} + // Provides helpers to the editor like validation for multi-line edits, completion candidates for // tab completion. #[derive(Helper, Hinter)] struct EditorHelper { context_id: u64, - message_tx: Sender<(String, Option)>, - response_rx: RefCell>>, + message_tx: Sender<(&'static str, Option)>, + response_rx: RefCell, AnyError>>>, } impl EditorHelper { pub fn get_global_lexical_scope_names(&self) -> Vec { - let evaluate_response = self + let resp = self .post_message( "Runtime.globalLexicalScopeNames", Some(json!({ @@ -55,14 +105,15 @@ impl EditorHelper { ) .unwrap(); - evaluate_response - .get("names") - .unwrap() - .as_array() - .unwrap() - .iter() - .map(|n| n.as_str().unwrap().to_string()) - .collect() + #[derive(Deserialize)] + struct GlobalLexicalScopeNamesResponse { + names: Vec, + } + + let response: GlobalLexicalScopeNamesResponse = + serde_json::from_str(resp.get()).unwrap(); + + response.names.into_iter().map(|name| name.into()).collect() } pub fn get_expression_property_names(&self, expr: &str) -> Vec { @@ -90,12 +141,7 @@ impl EditorHelper { } fn get_expression_type(&self, expr: &str) -> Option { - self - .evaluate_expression(expr)? - .get("result")? - .get("type")? - .as_str() - .map(|s| s.to_string()) + Some(self.evaluate_expression(expr)?.r#type) } fn get_object_expr_properties( @@ -103,9 +149,9 @@ impl EditorHelper { object_expr: &str, ) -> Option> { let evaluate_result = self.evaluate_expression(object_expr)?; - let object_id = evaluate_result.get("result")?.get("objectId")?; + let object_id = evaluate_result.object_id?; - let get_properties_response = self + let resp = self .post_message( "Runtime.getProperties", Some(json!({ @@ -114,19 +160,30 @@ impl EditorHelper { ) .ok()?; + #[derive(Deserialize)] + struct Property { + name: LossyString, + } + + #[derive(Deserialize)] + struct GetPropertiesResponse { + result: Option>, + } + + let get_properties_response: GetPropertiesResponse = + serde_json::from_str(resp.get()).unwrap(); + Some( get_properties_response - .get("result")? - .as_array() - .unwrap() - .iter() - .map(|r| r.get("name").unwrap().as_str().unwrap().to_string()) + .result? + .into_iter() + .map(|r| r.name.into()) .collect(), ) } - fn evaluate_expression(&self, expr: &str) -> Option { - let evaluate_response = self + fn evaluate_expression(&self, expr: &str) -> Option { + let resp = self .post_message( "Runtime.evaluate", Some(json!({ @@ -138,21 +195,22 @@ impl EditorHelper { ) .ok()?; - if evaluate_response.get("exceptionDetails").is_some() { + let evaluate_response: EvaluationResponse = + serde_json::from_str(resp.get()).unwrap(); + + if evaluate_response.exception_details.is_some() { None } else { - Some(evaluate_response) + evaluate_response.result } } fn post_message( &self, - method: &str, + method: &'static str, params: Option, - ) -> Result { - self - .message_tx - .blocking_send((method.to_string(), params))?; + ) -> Result, AnyError> { + self.message_tx.blocking_send((method, params))?; self.response_rx.borrow_mut().blocking_recv().unwrap() } } @@ -448,7 +506,9 @@ impl ReplSession { worker .with_event_loop( - session.post_message("Runtime.enable", None).boxed_local(), + session + .post_message("Runtime.enable", None::<()>) + .boxed_local(), ) .await?; @@ -457,17 +517,17 @@ impl ReplSession { // our inspector does not support a default context (0 is an invalid context id). let mut context_id: u64 = 0; for notification in session.notifications() { - let method = notification.get("method").unwrap().as_str().unwrap(); - let params = notification.get("params").unwrap(); - - if method == "Runtime.executionContextCreated" { - context_id = params - .get("context") - .unwrap() - .get("id") - .unwrap() - .as_u64() - .unwrap(); + if notification.method == "Runtime.executionContextCreated" { + #[derive(Deserialize)] + struct Params { + context: ExecutionContextDescription, + } + #[derive(Deserialize)] + struct ExecutionContextDescription { + id: u64, + } + let params: Params = serde_json::from_str(notification.params.get())?; + context_id = params.context.id; } } @@ -484,24 +544,19 @@ impl ReplSession { } pub async fn is_closing(&mut self) -> Result { - let closed = self - .evaluate_expression("(this.closed)") - .await? - .get("result") - .unwrap() - .get("value") - .unwrap() - .as_bool() - .unwrap(); + let resp = self.evaluate_expression("(this.closed)").await?; + + let closed = + serde_json::from_str(resp.result.unwrap().value.unwrap().get()).unwrap(); Ok(closed) } - pub async fn post_message_with_event_loop( + pub async fn post_message_with_event_loop( &mut self, - method: &str, - params: Option, - ) -> Result { + method: &'static str, + params: Option, + ) -> Result, AnyError> { self .worker .with_event_loop(self.session.post_message(method, params).boxed_local()) @@ -518,17 +573,16 @@ impl ReplSession { ) -> Result { match self.evaluate_line_with_object_wrapping(line).await { Ok(evaluate_response) => { - let evaluate_result = evaluate_response.get("result").unwrap(); - let evaluate_exception_details = - evaluate_response.get("exceptionDetails"); + let eval_res: CallArgument = evaluate_response.result.unwrap().into(); + let evaluate_exception_details = evaluate_response.exception_details; if evaluate_exception_details.is_some() { - self.set_last_thrown_error(evaluate_result).await?; + self.set_last_thrown_error(&eval_res).await?; } else { - self.set_last_eval_result(evaluate_result).await?; + self.set_last_eval_result(&eval_res).await?; } - let value = self.get_eval_value(evaluate_result).await?; + let value = self.get_eval_value(&eval_res).await?; Ok(match evaluate_exception_details { Some(_) => EvaluationOutput::Error(format!("Uncaught {}", value)), None => EvaluationOutput::Value(value), @@ -553,7 +607,7 @@ impl ReplSession { async fn evaluate_line_with_object_wrapping( &mut self, line: &str, - ) -> Result { + ) -> Result { // It is a bit unexpected that { "foo": "bar" } is interpreted as a block // statement rather than an object literal so we interpret it as an expression statement // to match the behavior found in a typical prompt including browser developer tools. @@ -569,21 +623,20 @@ impl ReplSession { // If that fails, we retry it without wrapping in parens letting the error bubble up to the // user if it is still an error. - let evaluate_response = - if evaluate_response.get("exceptionDetails").is_some() - && wrapped_line != line - { - self.evaluate_ts_expression(line).await? - } else { - evaluate_response - }; + let evaluate_response = if evaluate_response.exception_details.is_some() + && wrapped_line != line + { + self.evaluate_ts_expression(line).await? + } else { + evaluate_response + }; Ok(evaluate_response) } async fn set_last_thrown_error( &mut self, - error: &Value, + error: &CallArgument, ) -> Result<(), AnyError> { self.post_message_with_event_loop( "Runtime.callFunctionOn", @@ -600,55 +653,63 @@ impl ReplSession { async fn set_last_eval_result( &mut self, - evaluate_result: &Value, + evaluate_result: &CallArgument, ) -> Result<(), AnyError> { - self.post_message_with_event_loop( - "Runtime.callFunctionOn", - Some(json!({ - "executionContextId": self.context_id, - "functionDeclaration": "function (object) { Deno[Deno.internal].lastEvalResult = object; }", - "arguments": [ - evaluate_result, - ], - })), - ).await?; + let params = CallFunctionRequest { + execution_context_id: self.context_id, + function_declaration: + "function (object) { Deno[Deno.internal].lastEvalResult = object; }", + arguments: &[evaluate_result], + }; + self + .post_message_with_event_loop("Runtime.callFunctionOn", Some(params)) + .await?; Ok(()) } pub async fn get_eval_value( &mut self, - evaluate_result: &Value, + evaluate_result: &CallArgument, ) -> Result { + let params = CallFunctionRequest { + execution_context_id: self.context_id, + function_declaration: r#"function (object) { + try { + return Deno[Deno.internal].inspectArgs(["%o", object], { colors: !Deno.noColor }); + } catch (err) { + return Deno[Deno.internal].inspectArgs(["%o", err]); + } + }"#, + arguments: &[evaluate_result], + }; + // TODO(caspervonb) we should investigate using previews here but to keep things // consistent with the previous implementation we just get the preview result from // Deno.inspectArgs. - let inspect_response = self.post_message_with_event_loop( - "Runtime.callFunctionOn", - Some(json!({ - "executionContextId": self.context_id, - "functionDeclaration": r#"function (object) { - try { - return Deno[Deno.internal].inspectArgs(["%o", object], { colors: !Deno.noColor }); - } catch (err) { - return Deno[Deno.internal].inspectArgs(["%o", err]); - } - }"#, - "arguments": [ - evaluate_result, - ], - })), - ).await?; + let inspect_response = self + .post_message_with_event_loop("Runtime.callFunctionOn", Some(params)) + .await?; - let inspect_result = inspect_response.get("result").unwrap(); - let value = inspect_result.get("value").unwrap().as_str().unwrap(); + #[derive(Deserialize)] + struct CallFunctionResult { + value: LossyString, + } + + #[derive(Deserialize)] + struct CallFunctionResponse { + result: CallFunctionResult, + } + + let resp: CallFunctionResponse = + serde_json::from_str(inspect_response.get()).unwrap(); - Ok(value.to_string()) + Ok(resp.result.value.into()) } async fn evaluate_ts_expression( &mut self, expression: &str, - ) -> Result { + ) -> Result { let parsed_module = deno_ast::parse_module(deno_ast::ParseParams { specifier: "repl.ts".to_string(), source: deno_ast::SourceTextInfo::from_string(expression.to_string()), @@ -689,8 +750,8 @@ impl ReplSession { async fn evaluate_expression( &mut self, expression: &str, - ) -> Result { - self + ) -> Result { + let resp = self .post_message_with_event_loop( "Runtime.evaluate", Some(json!({ @@ -699,14 +760,17 @@ impl ReplSession { "replMode": true, })), ) - .await + .await?; + + let evaluate_resp: EvaluationResponse = serde_json::from_str(resp.get())?; + Ok(evaluate_resp) } } async fn read_line_and_poll( repl_session: &mut ReplSession, - message_rx: &mut Receiver<(String, Option)>, - response_tx: &UnboundedSender>, + message_rx: &mut Receiver<(&'static str, Option)>, + response_tx: &UnboundedSender, AnyError>>, editor: ReplEditor, ) -> Result { let mut line_fut = tokio::task::spawn_blocking(move || editor.readline()); diff --git a/core/Cargo.toml b/core/Cargo.toml index 5924d82fd2ec4..3370e46ad5b4c 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -23,7 +23,7 @@ log = "0.4.14" parking_lot = "0.11.1" pin-project = "1.0.7" serde = { version = "1.0.129", features = ["derive"] } -serde_json = { version = "1.0.66", features = ["preserve_order"] } +serde_json = { version = "1.0.72", features = ["preserve_order", "raw_value"] } serde_v8 = { version = "0.20.0", path = "../serde_v8" } url = { version = "2.2.2", features = ["serde"] } v8 = "0.35.0" diff --git a/core/inspector.rs b/core/inspector.rs index 8a686dc635867..feb5e727922f2 100644 --- a/core/inspector.rs +++ b/core/inspector.rs @@ -19,14 +19,17 @@ use crate::futures::task; use crate::futures::task::Context; use crate::futures::task::Poll; use crate::serde_json; -use crate::serde_json::json; -use crate::serde_json::Value; use anyhow::Error; +use log::debug; use parking_lot::Mutex; +use serde::Deserialize; +use serde::Serialize; +use serde_json::value::RawValue; use std::cell::BorrowMutError; use std::cell::RefCell; use std::collections::HashMap; use std::ffi::c_void; +use std::iter::Peekable; use std::mem::replace; use std::mem::take; use std::mem::MaybeUninit; @@ -34,6 +37,7 @@ use std::pin::Pin; use std::ptr; use std::ptr::NonNull; use std::rc::Rc; +use std::str::Chars; use std::sync::Arc; use std::thread; @@ -600,14 +604,33 @@ impl Future for InspectorSession { } } +#[derive(Debug, Deserialize)] +struct V8InspectorMessage { + id: i32, + result: Option>, + error: Option, +} + +#[derive(Debug, Deserialize)] +struct V8Error { + code: i32, + message: LossyString, +} + +#[derive(Deserialize)] +pub struct V8InspectorNotification { + pub method: String, + pub params: Box, +} + /// A local inspector session that can be used to send and receive protocol messages directly on /// the same thread as an isolate. pub struct LocalInspectorSession { v8_session_tx: UnboundedSender>, v8_session_rx: UnboundedReceiver<(Option, String)>, - response_tx_map: HashMap>, + response_tx_map: HashMap>, next_message_id: i32, - notification_queue: Vec, + notification_queue: Vec, } impl LocalInspectorSession { @@ -629,29 +652,33 @@ impl LocalInspectorSession { } } - pub fn notifications(&mut self) -> Vec { + pub fn notifications(&mut self) -> Vec { self.notification_queue.split_off(0) } - pub async fn post_message( + pub async fn post_message( &mut self, - method: &str, - params: Option, - ) -> Result { + method: &'static str, + params: Option, + ) -> Result, Error> { let id = self.next_message_id; self.next_message_id += 1; let (response_tx, mut response_rx) = - oneshot::channel::(); + oneshot::channel::(); self.response_tx_map.insert(id, response_tx); - let message = json!({ - "id": id, - "method": method, - "params": params, - }); + #[derive(Serialize)] + struct Request { + id: i32, + method: &'static str, + params: Option, + } + + let message = Request { id, method, params }; let raw_message = serde_json::to_string(&message).unwrap(); + debug!("Sending message: {}", raw_message); self .v8_session_tx .unbounded_send(raw_message.as_bytes().to_vec()) @@ -663,12 +690,16 @@ impl LocalInspectorSession { Either::Left(_) => continue, Either::Right((result, _)) => { let response = result?; - if let Some(error) = response.get("error") { - return Err(generic_error(error.to_string())); + match (response.result, response.error) { + (Some(result), None) => { + return Ok(result); + } + (None, Some(err)) => { + let message: String = err.message.into(); + return Err(generic_error(message)); + } + _ => panic!("Invalid response"), } - - let result = response.get("result").unwrap().clone(); - return Ok(result); } } } @@ -676,31 +707,15 @@ impl LocalInspectorSession { async fn receive_from_v8_session(&mut self) { let (maybe_call_id, message) = self.v8_session_rx.next().await.unwrap(); + debug!("Recv message: {}", message); // If there's no call_id then it's a notification if let Some(call_id) = maybe_call_id { - let message: serde_json::Value = match serde_json::from_str(&message) { + let message: V8InspectorMessage = match serde_json::from_str(&message) { Ok(v) => v, - Err(error) => match error.classify() { - serde_json::error::Category::Syntax => json!({ - "id": call_id, - "result": { - "result": { - "type": "error", - "description": "Unterminated string literal", - "value": "Unterminated string literal", - }, - "exceptionDetails": { - "exceptionId": 0, - "text": "Unterminated string literal", - "lineNumber": 0, - "columnNumber": 0 - }, - }, - }), - _ => panic!("Could not parse inspector message"), - }, + Err(e) => { + panic!("Could not parse inspector message: {}", e) + } }; - self .response_tx_map .remove(&call_id) @@ -720,3 +735,244 @@ fn new_box_with(new_fn: impl FnOnce(*mut T) -> T) -> Box { unsafe { ptr::write(p, new_fn(p)) }; unsafe { Box::from_raw(p) } } + +/// A wrapper type for `String` that has a serde::Deserialize implementation +/// that will deserialize lossily (i.e. using replacement characters for +/// invalid UTF-8 sequences). +pub struct LossyString(String); + +static ESCAPE: [bool; 256] = { + const CT: bool = true; // control character \x00..=\x1F + const QU: bool = true; // quote \x22 + const BS: bool = true; // backslash \x5C + const __: bool = false; // allow unescaped + [ + // 1 2 3 4 5 6 7 8 9 A B C D E F + CT, CT, CT, CT, CT, CT, CT, CT, CT, CT, CT, CT, CT, CT, CT, CT, // 0 + CT, CT, CT, CT, CT, CT, CT, CT, CT, CT, CT, CT, CT, CT, CT, CT, // 1 + __, __, QU, __, __, __, __, __, __, __, __, __, __, __, __, __, // 2 + __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 3 + __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 4 + __, __, __, __, __, __, __, __, __, __, __, __, BS, __, __, __, // 5 + __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 6 + __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 7 + __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 8 + __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 9 + __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // A + __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // B + __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // C + __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // D + __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // E + __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // F + ] +}; + +impl<'de> serde::Deserialize<'de> for LossyString { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + macro_rules! tri { + ($opt:expr) => { + match $opt { + Some(v) => v, + None => { + return Err(serde::de::Error::custom("Unexpected end of string")) + } + } + }; + } + + let val = Box::::deserialize(deserializer)?; + let mut chars = val.get().chars().peekable(); + + if tri!(chars.next()) != '"' { + return Err(serde::de::Error::custom("Expected string")); + } + + let mut str = String::new(); + + loop { + let ch = tri!(chars.next()); + if !ESCAPE[ch as usize] { + str.push(ch); + continue; + } + match ch { + '"' => { + break; + } + '\\' => match parse_escape(&mut chars, &mut str) { + Ok(_) => {} + Err(err) => return Err(serde::de::Error::custom(err)), + }, + _ => { + return Err(serde::de::Error::custom("Constrol character in string")); + } + } + } + + if chars.next() != None { + return Err(serde::de::Error::custom("Trailing characters in string")); + } + + Ok(LossyString(str)) + } +} + +static HEX: [u8; 256] = { + const __: u8 = 255; // not a hex digit + [ + // 1 2 3 4 5 6 7 8 9 A B C D E F + __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 0 + __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 1 + __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 2 + 00, 01, 02, 03, 04, 05, 06, 07, 08, 09, __, __, __, __, __, __, // 3 + __, 10, 11, 12, 13, 14, 15, __, __, __, __, __, __, __, __, __, // 4 + __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 5 + __, 10, 11, 12, 13, 14, 15, __, __, __, __, __, __, __, __, __, // 6 + __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 7 + __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 8 + __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 9 + __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // A + __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // B + __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // C + __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // D + __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // E + __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // F + ] +}; + +fn decode_hex_val(val: char) -> Option { + let n = HEX[val as usize] as u16; + if n == 255 { + None + } else { + Some(n) + } +} + +fn decode_hex_escape( + chars: &mut Peekable>, +) -> Result { + let mut n = 0; + for _ in 0..4 { + let ch = match chars.next() { + Some(ch) => ch, + None => return Err("Unexpected end of string"), + }; + match decode_hex_val(ch) { + None => return Err("Invalid hex escape"), + Some(val) => { + n = (n << 4) + val; + } + } + } + Ok(n) +} + +fn parse_escape( + chars: &mut Peekable>, + str: &mut String, +) -> Result<(), &'static str> { + macro_rules! tri { + ($opt:expr) => { + match $opt { + Some(v) => v, + None => return Err("Unexpected end of string"), + } + }; + } + + let ch = tri!(chars.next()); + + match ch { + '"' => str.push('"'), + '\\' => str.push('\\'), + '/' => str.push('/'), + 'b' => str.push('\x08'), + 'f' => str.push('\x0c'), + 'n' => str.push('\n'), + 'r' => str.push('\r'), + 't' => str.push('\t'), + 'u' => { + let c = match decode_hex_escape(chars)? { + 0xDC00..=0xDFFF => { + str.push('\u{FFFD}'); + return Ok(()); + } + + // Non-BMP characters are encoded as a sequence of two hex + // escapes, representing UTF-16 surrogates. If deserializing a + // utf-8 string the surrogates are required to be paired, + // whereas deserializing a byte string accepts lone surrogates. + n1 @ 0xD800..=0xDBFF => { + if *tri!(chars.peek()) == '\\' { + chars.next(); + } else { + str.push('\u{FFFD}'); + return Ok(()); + } + + if *tri!(chars.peek()) == 'u' { + chars.next(); + } else { + str.push('\u{FFFD}'); + return parse_escape(chars, str); + } + + let n2 = decode_hex_escape(chars)?; + + if n2 < 0xDC00 || n2 > 0xDFFF { + str.push('\u{FFFD}'); + } + + let n = + (((n1 - 0xD800) as u32) << 10 | (n2 - 0xDC00) as u32) + 0x1_0000; + + match char::from_u32(n) { + Some(c) => c, + None => { + str.push('\u{FFFD}'); + return Ok(()); + } + } + } + + n => match char::from_u32(n as u32) { + Some(c) => c, + None => { + str.push('\u{FFFD}'); + return Ok(()); + } + }, + }; + + str.push(c); + } + _ => { + return Err("Invalid escape sequence"); + } + } + + Ok(()) +} + +impl From for String { + fn from(lossy_string: LossyString) -> Self { + lossy_string.0 + } +} + +impl std::ops::Deref for LossyString { + type Target = String; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl std::fmt::Debug for LossyString { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) + } +} diff --git a/core/lib.rs b/core/lib.rs index 87994720f40c3..e43d28f9bac46 100644 --- a/core/lib.rs +++ b/core/lib.rs @@ -48,6 +48,8 @@ pub use crate::flags::v8_set_flags; pub use crate::inspector::InspectorSessionProxy; pub use crate::inspector::JsRuntimeInspector; pub use crate::inspector::LocalInspectorSession; +pub use crate::inspector::LossyString; +pub use crate::inspector::V8InspectorNotification; pub use crate::module_specifier::resolve_import; pub use crate::module_specifier::resolve_path; pub use crate::module_specifier::resolve_url; From 79e4ddcf47a03826ba76f6a48ff0422df4cb82d4 Mon Sep 17 00:00:00 2001 From: David Sherret Date: Thu, 25 Nov 2021 14:09:25 -0500 Subject: [PATCH 2/4] Format --- cli/tools/repl/channel.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/cli/tools/repl/channel.rs b/cli/tools/repl/channel.rs index c4a0590bb93a1..f9a35df956be7 100644 --- a/cli/tools/repl/channel.rs +++ b/cli/tools/repl/channel.rs @@ -45,9 +45,7 @@ impl RustylineSyncMessageSender { method: &'static str, params: Option, ) -> Result, AnyError> { - if let Err(err) = - self.message_tx.blocking_send((method, params)) - { + if let Err(err) = self.message_tx.blocking_send((method, params)) { Err(anyhow!("{}", err)) } else { self.response_rx.borrow_mut().blocking_recv().unwrap() From c9df219c11c1a2cdcc8747055fdce124b605c01c Mon Sep 17 00:00:00 2001 From: David Sherret Date: Thu, 25 Nov 2021 14:24:56 -0500 Subject: [PATCH 3/4] Fix issues. --- cli/tools/repl/channel.rs | 1 + cli/tools/repl/mod.rs | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/tools/repl/channel.rs b/cli/tools/repl/channel.rs index f9a35df956be7..697abb1fbd0d8 100644 --- a/cli/tools/repl/channel.rs +++ b/cli/tools/repl/channel.rs @@ -2,6 +2,7 @@ use deno_core::anyhow::anyhow; use deno_core::error::AnyError; +use deno_core::serde_json::value::RawValue; use deno_core::serde_json::Value; use std::cell::RefCell; use tokio::sync::mpsc::channel; diff --git a/cli/tools/repl/mod.rs b/cli/tools/repl/mod.rs index 18c8cf1e0f210..84faeadc57fde 100644 --- a/cli/tools/repl/mod.rs +++ b/cli/tools/repl/mod.rs @@ -13,7 +13,6 @@ use deno_core::parking_lot::Mutex; use deno_core::serde_json; use deno_core::serde_json::json; use deno_core::serde_json::value::RawValue; -use deno_core::serde_json::Value; use deno_core::LocalInspectorSession; use deno_core::LossyString; use deno_runtime::worker::MainWorker; From 6e2eed9756f8bae2bde7363a50a5c6912f69d3a5 Mon Sep 17 00:00:00 2001 From: Luca Casonato Date: Thu, 25 Nov 2021 22:34:45 +0100 Subject: [PATCH 4/4] fix --- core/inspector.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/core/inspector.rs b/core/inspector.rs index feb5e727922f2..9d87b9e5b0a44 100644 --- a/core/inspector.rs +++ b/core/inspector.rs @@ -925,6 +925,7 @@ fn parse_escape( if n2 < 0xDC00 || n2 > 0xDFFF { str.push('\u{FFFD}'); + return Ok(()); } let n =