From aad860db843edc35f3a14f7fe99259a8fac6aa75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=28=C2=B4=E2=8C=A3=60=CA=83=C6=AA=29?= Date: Sat, 3 Oct 2020 13:13:25 -0700 Subject: [PATCH] Throw TypeError if regex is passed to startsWith, endsWith, includes (#782) --- boa/src/builtins/string/mod.rs | 49 +++++++++++++------ boa/src/builtins/string/tests.rs | 84 ++++++++++++++++++++++++++++++++ 2 files changed, 118 insertions(+), 15 deletions(-) diff --git a/boa/src/builtins/string/mod.rs b/boa/src/builtins/string/mod.rs index 1d92b24be2..cc33802623 100644 --- a/boa/src/builtins/string/mod.rs +++ b/boa/src/builtins/string/mod.rs @@ -331,11 +331,15 @@ impl String { // Then we convert it into a Rust String by wrapping it in from_value let primitive_val = this.to_string(ctx)?; - // TODO: Should throw TypeError if pattern is regular expression - let search_string = args - .get(0) - .expect("failed to get argument for String method") - .to_string(ctx)?; + let arg = args.get(0).cloned().unwrap_or_else(Value::undefined); + + if Self::is_regexp_object(&arg) { + ctx.throw_type_error( + "First argument to String.prototype.startsWith must not be a regular expression", + )?; + } + + let search_string = arg.to_string(ctx)?; let length = primitive_val.chars().count() as i32; let search_length = search_string.chars().count() as i32; @@ -374,11 +378,15 @@ impl String { // Then we convert it into a Rust String by wrapping it in from_value let primitive_val = this.to_string(ctx)?; - // TODO: Should throw TypeError if search_string is regular expression - let search_string = args - .get(0) - .expect("failed to get argument for String method") - .to_string(ctx)?; + let arg = args.get(0).cloned().unwrap_or_else(Value::undefined); + + if Self::is_regexp_object(&arg) { + ctx.throw_type_error( + "First argument to String.prototype.endsWith must not be a regular expression", + )?; + } + + let search_string = arg.to_string(ctx)?; let length = primitive_val.chars().count() as i32; let search_length = search_string.chars().count() as i32; @@ -420,11 +428,15 @@ impl String { // Then we convert it into a Rust String by wrapping it in from_value let primitive_val = this.to_string(ctx)?; - // TODO: Should throw TypeError if search_string is regular expression - let search_string = args - .get(0) - .expect("failed to get argument for String method") - .to_string(ctx)?; + let arg = args.get(0).cloned().unwrap_or_else(Value::undefined); + + if Self::is_regexp_object(&arg) { + ctx.throw_type_error( + "First argument to String.prototype.includes must not be a regular expression", + )?; + } + + let search_string = arg.to_string(ctx)?; let length = primitive_val.chars().count() as i32; @@ -462,6 +474,13 @@ impl String { } } + fn is_regexp_object(value: &Value) -> bool { + match value { + Value::Object(ref obj) => obj.borrow().is_regexp(), + _ => false, + } + } + /// `String.prototype.replace( regexp|substr, newSubstr|function )` /// /// The `replace()` method returns a new string with some or all matches of a `pattern` replaced by a `replacement`. diff --git a/boa/src/builtins/string/tests.rs b/boa/src/builtins/string/tests.rs index 3bc8ea1951..68ef1439c2 100644 --- a/boa/src/builtins/string/tests.rs +++ b/boa/src/builtins/string/tests.rs @@ -320,6 +320,26 @@ fn starts_with() { assert_eq!(forward(&mut engine, "zhLiteral.startsWith('中')"), "true"); } +#[test] +fn starts_with_with_regex_arg() { + let mut engine = Context::new(); + + let scenario = r#" + try { + 'Saturday night'.startsWith(/Saturday/); + } catch (e) { + e.toString(); + } + "#; + + assert_eq!( + forward( + &mut engine, scenario + ), + "\"TypeError: First argument to String.prototype.startsWith must not be a regular expression\"" + ) +} + #[test] fn ends_with() { let mut engine = Context::new(); @@ -344,6 +364,70 @@ fn ends_with() { assert_eq!(forward(&mut engine, "zhLiteral.endsWith('文')"), "true"); } +#[test] +fn ends_with_with_regex_arg() { + let mut engine = Context::new(); + + let scenario = r#" + try { + 'Saturday night'.endsWith(/night/); + } catch (e) { + e.toString(); + } + "#; + + assert_eq!( + forward( + &mut engine, scenario + ), + "\"TypeError: First argument to String.prototype.endsWith must not be a regular expression\"" + ) +} + +#[test] +fn includes() { + let mut engine = Context::new(); + let init = r#" + var empty = new String(''); + var en = new String('english'); + var zh = new String('中文'); + + var emptyLiteral = ''; + var enLiteral = 'english'; + var zhLiteral = '中文'; + "#; + + forward(&mut engine, init); + + assert_eq!(forward(&mut engine, "empty.includes('')"), "true"); + assert_eq!(forward(&mut engine, "en.includes('g')"), "true"); + assert_eq!(forward(&mut engine, "zh.includes('文')"), "true"); + + assert_eq!(forward(&mut engine, "emptyLiteral.includes('')"), "true"); + assert_eq!(forward(&mut engine, "enLiteral.includes('g')"), "true"); + assert_eq!(forward(&mut engine, "zhLiteral.includes('文')"), "true"); +} + +#[test] +fn includes_with_regex_arg() { + let mut engine = Context::new(); + + let scenario = r#" + try { + 'Saturday night'.includes(/day/); + } catch (e) { + e.toString(); + } + "#; + + assert_eq!( + forward( + &mut engine, scenario + ), + "\"TypeError: First argument to String.prototype.includes must not be a regular expression\"" + ) +} + #[test] fn match_all() { let mut engine = Context::new();