0
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2025-03-03 09:31:22 -05:00

fix(repl): do not panic deleting Deno or deleting all its properties (#18211)

Closes #18194
Closes #12092
This commit is contained in:
David Sherret 2023-03-15 21:41:13 -04:00 committed by GitHub
parent 48a0b7f98f
commit c1eba16b84
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 140 additions and 66 deletions

View file

@ -222,6 +222,36 @@ fn pty_assign_global_this() {
});
}
#[test]
fn pty_assign_deno_keys_and_deno() {
util::with_pty(&["repl"], |mut console| {
console.write_line(
"Object.keys(Deno).forEach((key)=>{try{Deno[key] = undefined} catch {}})",
);
console.write_line("delete globalThis.Deno");
console.write_line("console.log('testing ' + 'this out')");
console.write_line("close();");
let output = console.read_all_output();
assert_not_contains!(output, "panicked");
assert_contains!(output, "testing this out");
});
}
#[test]
fn pty_internal_repl() {
util::with_pty(&["repl"], |mut console| {
console.write_line("globalThis");
console.write_line("__\t\t");
console.write_line("close();");
let output = console.read_all_output();
assert_contains!(output, "__defineGetter__");
// should not contain the internal repl variable
// in the `globalThis` or completions output
assert_not_contains!(output, "__DENO_");
});
}
#[test]
fn pty_emoji() {
// windows was having issues displaying this

View file

@ -39,6 +39,7 @@ use std::sync::Arc;
use super::cdp;
use super::channel::RustylineSyncMessageSender;
use super::session::REPL_INTERNALS_NAME;
// Provides helpers to the editor like validation for multi-line edits, completion candidates for
// tab completion.
@ -159,7 +160,7 @@ impl EditorHelper {
}
fn is_word_boundary(c: char) -> bool {
if c == '.' {
if matches!(c, '.' | '_' | '$') {
false
} else {
char::is_ascii_whitespace(&c) || char::is_ascii_punctuation(&c)
@ -207,7 +208,11 @@ impl Completer for EditorHelper {
let candidates = self
.get_expression_property_names(sub_expr)
.into_iter()
.filter(|n| !n.starts_with("Symbol(") && n.starts_with(prop_name))
.filter(|n| {
!n.starts_with("Symbol(")
&& n.starts_with(prop_name)
&& n != &*REPL_INTERNALS_NAME
})
.collect();
Ok((pos - prop_name.len(), candidates))
@ -217,7 +222,7 @@ impl Completer for EditorHelper {
.get_expression_property_names("globalThis")
.into_iter()
.chain(self.get_global_lexical_scope_names())
.filter(|n| n.starts_with(expr))
.filter(|n| n.starts_with(expr) && n != &*REPL_INTERNALS_NAME)
.collect::<Vec<_>>();
// sort and remove duplicates

View file

@ -22,41 +22,69 @@ use deno_graph::npm::NpmPackageReqReference;
use deno_graph::source::Resolver;
use deno_runtime::deno_node;
use deno_runtime::worker::MainWorker;
use once_cell::sync::Lazy;
use super::cdp;
static PRELUDE: &str = r#"
Object.defineProperty(globalThis, "_", {
configurable: true,
get: () => Deno[Deno.internal].lastEvalResult,
set: (value) => {
Object.defineProperty(globalThis, "_", {
value: value,
writable: true,
enumerable: true,
configurable: true,
});
console.log("Last evaluation result is no longer saved to _.");
},
/// We store functions used in the repl on this object because
/// the user might modify the `Deno` global or delete it outright.
pub static REPL_INTERNALS_NAME: Lazy<String> = Lazy::new(|| {
let now = std::time::SystemTime::now();
let seconds = now
.duration_since(std::time::SystemTime::UNIX_EPOCH)
.unwrap()
.as_secs();
// use a changing variable name to make it hard to depend on this
format!("__DENO_REPL_INTERNALS_{seconds}__")
});
Object.defineProperty(globalThis, "_error", {
fn get_prelude() -> String {
format!(
r#"
Object.defineProperty(globalThis, "{0}", {{
enumerable: false,
writable: false,
value: {{
lastEvalResult: undefined,
lastThrownError: undefined,
inspectArgs: Deno[Deno.internal].inspectArgs,
noColor: Deno.noColor,
}},
}});
Object.defineProperty(globalThis, "_", {{
configurable: true,
get: () => Deno[Deno.internal].lastThrownError,
set: (value) => {
Object.defineProperty(globalThis, "_error", {
get: () => {0}.lastEvalResult,
set: (value) => {{
Object.defineProperty(globalThis, "_", {{
value: value,
writable: true,
enumerable: true,
configurable: true,
});
}});
console.log("Last evaluation result is no longer saved to _.");
}},
}});
Object.defineProperty(globalThis, "_error", {{
configurable: true,
get: () => {0}.lastThrownError,
set: (value) => {{
Object.defineProperty(globalThis, "_error", {{
value: value,
writable: true,
enumerable: true,
configurable: true,
}});
console.log("Last thrown error is no longer saved to _error.");
},
});
}},
}});
globalThis.clear = console.clear.bind(console);
"#;
"#,
*REPL_INTERNALS_NAME
)
}
pub enum EvaluationOutput {
Value(String),
@ -161,7 +189,7 @@ impl ReplSession {
};
// inject prelude
repl_session.evaluate_expression(PRELUDE).await?;
repl_session.evaluate_expression(&get_prelude()).await?;
Ok(repl_session)
}
@ -307,22 +335,27 @@ impl ReplSession {
&mut self,
error: &cdp::RemoteObject,
) -> Result<(), AnyError> {
self.post_message_with_event_loop(
"Runtime.callFunctionOn",
Some(cdp::CallFunctionOnArgs {
function_declaration: "function (object) { Deno[Deno.internal].lastThrownError = object; }".to_string(),
object_id: None,
arguments: Some(vec![error.into()]),
silent: None,
return_by_value: None,
generate_preview: None,
user_gesture: None,
await_promise: None,
execution_context_id: Some(self.context_id),
object_group: None,
throw_on_side_effect: None
}),
).await?;
self
.post_message_with_event_loop(
"Runtime.callFunctionOn",
Some(cdp::CallFunctionOnArgs {
function_declaration: format!(
r#"function (object) {{ {}.lastThrownError = object; }}"#,
*REPL_INTERNALS_NAME
),
object_id: None,
arguments: Some(vec![error.into()]),
silent: None,
return_by_value: None,
generate_preview: None,
user_gesture: None,
await_promise: None,
execution_context_id: Some(self.context_id),
object_group: None,
throw_on_side_effect: None,
}),
)
.await?;
Ok(())
}
@ -334,9 +367,10 @@ impl ReplSession {
.post_message_with_event_loop(
"Runtime.callFunctionOn",
Some(cdp::CallFunctionOnArgs {
function_declaration:
"function (object) { Deno[Deno.internal].lastEvalResult = object; }"
.to_string(),
function_declaration: format!(
r#"function (object) {{ {}.lastEvalResult = object; }}"#,
*REPL_INTERNALS_NAME
),
object_id: None,
arguments: Some(vec![evaluate_result.into()]),
silent: None,
@ -360,28 +394,33 @@ impl ReplSession {
// 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(cdp::CallFunctionOnArgs {
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]);
}
}"#.to_string(),
object_id: None,
arguments: Some(vec![evaluate_result.into()]),
silent: None,
return_by_value: None,
generate_preview: None,
user_gesture: None,
await_promise: None,
execution_context_id: Some(self.context_id),
object_group: None,
throw_on_side_effect: None
}),
).await?;
let inspect_response = self
.post_message_with_event_loop(
"Runtime.callFunctionOn",
Some(cdp::CallFunctionOnArgs {
function_declaration: format!(
r#"function (object) {{
try {{
return {0}.inspectArgs(["%o", object], {{ colors: !{0}.noColor }});
}} catch (err) {{
return {0}.inspectArgs(["%o", err]);
}}
}}"#,
*REPL_INTERNALS_NAME
),
object_id: None,
arguments: Some(vec![evaluate_result.into()]),
silent: None,
return_by_value: None,
generate_preview: None,
user_gesture: None,
await_promise: None,
execution_context_id: Some(self.context_id),
object_group: None,
throw_on_side_effect: None,
}),
)
.await?;
let response: cdp::CallFunctionOnResponse =
serde_json::from_value(inspect_response)?;