avm1: Value::coerce_to_string returns an Avm1String, as it can avoid a clone-and-reallocate

This commit is contained in:
Nathan Adams 2020-07-13 01:29:04 +02:00 committed by Mike Welsh
parent f0ef68cb16
commit d8f043fa1a
14 changed files with 69 additions and 65 deletions

View File

@ -394,7 +394,7 @@ pub fn root_error_handler<'gc>(
if let Error::ThrownValue(error) = &error { if let Error::ThrownValue(error) = &error {
let string = error let string = error
.coerce_to_string(activation, context) .coerce_to_string(activation, context)
.unwrap_or_else(|_| Cow::Borrowed("undefined")); .unwrap_or_else(|_| "undefined".into());
log::info!(target: "avm_trace", "{}", string); log::info!(target: "avm_trace", "{}", string);
} else { } else {
log::error!("{}", error); log::error!("{}", error);

View File

@ -1299,7 +1299,7 @@ impl<'a, 'gc: 'a> Activation<'a, 'gc> {
.coerce_to_object(self, context); .coerce_to_object(self, context);
let (url, opts) = self.locals_into_request_options( let (url, opts) = self.locals_into_request_options(
context, context,
url, Cow::Borrowed(&url),
NavigationMethod::from_send_vars_method(swf_method), NavigationMethod::from_send_vars_method(swf_method),
); );
let fetch = context.navigator.fetch(&url, opts); let fetch = context.navigator.fetch(&url, opts);
@ -1317,7 +1317,7 @@ impl<'a, 'gc: 'a> Activation<'a, 'gc> {
if let Some(clip_target) = clip_target { if let Some(clip_target) = clip_target {
let (url, opts) = self.locals_into_request_options( let (url, opts) = self.locals_into_request_options(
context, context,
url, Cow::Borrowed(&url),
NavigationMethod::from_send_vars_method(swf_method), NavigationMethod::from_send_vars_method(swf_method),
); );
let fetch = context.navigator.fetch(&url, opts); let fetch = context.navigator.fetch(&url, opts);
@ -2277,7 +2277,7 @@ impl<'a, 'gc: 'a> Activation<'a, 'gc> {
// trace always prints "undefined" even though SWF6 and below normally // trace always prints "undefined" even though SWF6 and below normally
// coerce undefined to "". // coerce undefined to "".
let out = if val == Value::Undefined { let out = if val == Value::Undefined {
Cow::Borrowed("undefined") "undefined".into()
} else { } else {
val.coerce_to_string(self, context)? val.coerce_to_string(self, context)?
}; };
@ -2480,7 +2480,7 @@ impl<'a, 'gc: 'a> Activation<'a, 'gc> {
v.ok() v.ok()
.unwrap_or_else(|| Value::Undefined) .unwrap_or_else(|| Value::Undefined)
.coerce_to_string(self, context) .coerce_to_string(self, context)
.unwrap_or_else(|_| Cow::Borrowed("undefined")) .unwrap_or_else(|_| "undefined".into())
.to_string(), .to_string(),
); );
} }

View File

@ -198,7 +198,7 @@ mod tests {
use super::*; use super::*;
use crate::avm1::error::Error; use crate::avm1::error::Error;
use crate::avm1::test_utils::with_avm; use crate::avm1::test_utils::with_avm;
use crate::avm1::{Avm1String, ScriptObject}; use crate::avm1::ScriptObject;
#[test] #[test]
fn dump_undefined() { fn dump_undefined() {

View File

@ -167,7 +167,7 @@ pub fn create_timer<'a, 'gc>(
.get(1) .get(1)
.unwrap_or(&Value::Undefined) .unwrap_or(&Value::Undefined)
.coerce_to_string(activation, context)? .coerce_to_string(activation, context)?
.into_owned(), .to_string(),
}, },
2, 2,
), ),

View File

@ -8,7 +8,6 @@ use crate::avm1::property::Attribute;
use crate::avm1::{Avm1String, Object, ScriptObject, TObject, UpdateContext, Value}; use crate::avm1::{Avm1String, Object, ScriptObject, TObject, UpdateContext, Value};
use enumset::EnumSet; use enumset::EnumSet;
use gc_arena::MutationContext; use gc_arena::MutationContext;
use smallvec::alloc::borrow::Cow;
use std::cmp::Ordering; use std::cmp::Ordering;
// Flags used by `Array.sort` and `sortOn`. // Flags used by `Array.sort` and `sortOn`.
@ -244,7 +243,7 @@ pub fn join<'gc>(
let separator = args let separator = args
.get(0) .get(0)
.and_then(|v| v.coerce_to_string(activation, context).ok()) .and_then(|v| v.coerce_to_string(activation, context).ok())
.unwrap_or_else(|| Cow::Borrowed(",")); .unwrap_or_else(|| ",".into());
let values: Vec<Value<'gc>> = this.array(); let values: Vec<Value<'gc>> = this.array();
Ok(Avm1String::new( Ok(Avm1String::new(
@ -253,9 +252,10 @@ pub fn join<'gc>(
.iter() .iter()
.map(|v| { .map(|v| {
v.coerce_to_string(activation, context) v.coerce_to_string(activation, context)
.unwrap_or_else(|_| Cow::Borrowed("undefined")) .unwrap_or_else(|_| "undefined".into())
.to_string()
}) })
.collect::<Vec<Cow<str>>>() .collect::<Vec<String>>()
.join(&separator), .join(&separator),
) )
.into()) .into())

View File

@ -12,6 +12,7 @@ use crate::prelude::*;
use crate::shape_utils::DrawCommand; use crate::shape_utils::DrawCommand;
use crate::tag_utils::SwfSlice; use crate::tag_utils::SwfSlice;
use gc_arena::MutationContext; use gc_arena::MutationContext;
use std::borrow::Cow;
use swf::{ use swf::{
FillStyle, Gradient, GradientInterpolation, GradientRecord, GradientSpread, LineCapStyle, FillStyle, Gradient, GradientInterpolation, GradientRecord, GradientSpread, LineCapStyle,
LineJoinStyle, LineStyle, Twips, LineJoinStyle, LineStyle, Twips,
@ -1047,7 +1048,7 @@ fn load_movie<'gc>(
let url = url_val.coerce_to_string(activation, context)?; let url = url_val.coerce_to_string(activation, context)?;
let method = args.get(1).cloned().unwrap_or(Value::Undefined); let method = args.get(1).cloned().unwrap_or(Value::Undefined);
let method = NavigationMethod::from_method_str(&method.coerce_to_string(activation, context)?); let method = NavigationMethod::from_method_str(&method.coerce_to_string(activation, context)?);
let (url, opts) = activation.locals_into_request_options(context, url, method); let (url, opts) = activation.locals_into_request_options(context, Cow::Borrowed(&url), method);
let fetch = context.navigator.fetch(&url, opts); let fetch = context.navigator.fetch(&url, opts);
let process = context.load_manager.load_movie_into_clip( let process = context.load_manager.load_movie_into_clip(
context.player.clone().unwrap(), context.player.clone().unwrap(),
@ -1071,7 +1072,7 @@ fn load_variables<'gc>(
let url = url_val.coerce_to_string(activation, context)?; let url = url_val.coerce_to_string(activation, context)?;
let method = args.get(1).cloned().unwrap_or(Value::Undefined); let method = args.get(1).cloned().unwrap_or(Value::Undefined);
let method = NavigationMethod::from_method_str(&method.coerce_to_string(activation, context)?); let method = NavigationMethod::from_method_str(&method.coerce_to_string(activation, context)?);
let (url, opts) = activation.locals_into_request_options(context, url, method); let (url, opts) = activation.locals_into_request_options(context, Cow::Borrowed(&url), method);
let fetch = context.navigator.fetch(&url, opts); let fetch = context.navigator.fetch(&url, opts);
let target = target.object().coerce_to_object(activation, context); let target = target.object().coerce_to_object(activation, context);
let process = let process =

View File

@ -29,7 +29,7 @@ pub fn add_property<'gc>(
let name = args let name = args
.get(0) .get(0)
.and_then(|v| v.coerce_to_string(activation, context).ok()) .and_then(|v| v.coerce_to_string(activation, context).ok())
.unwrap_or_else(|| Cow::Borrowed("undefined")); .unwrap_or_else(|| "undefined".into());
let getter = args.get(1).unwrap_or(&Value::Undefined); let getter = args.get(1).unwrap_or(&Value::Undefined);
let setter = args.get(2).unwrap_or(&Value::Undefined); let setter = args.get(2).unwrap_or(&Value::Undefined);
@ -188,7 +188,13 @@ fn watch<'gc>(
}; };
let user_data = args.get(2).cloned().unwrap_or(Value::Undefined); let user_data = args.get(2).cloned().unwrap_or(Value::Undefined);
this.set_watcher(activation, context.gc_context, name, callback, user_data); this.set_watcher(
activation,
context.gc_context,
Cow::Borrowed(&name),
callback,
user_data,
);
Ok(true.into()) Ok(true.into())
} }
@ -206,7 +212,7 @@ fn unwatch<'gc>(
return Ok(false.into()); return Ok(false.into());
}; };
let result = this.remove_watcher(activation, context.gc_context, name); let result = this.remove_watcher(activation, context.gc_context, Cow::Borrowed(&name));
Ok(result.into()) Ok(result.into())
} }

View File

@ -20,10 +20,7 @@ pub fn string<'gc>(
) -> Result<Value<'gc>, Error<'gc>> { ) -> Result<Value<'gc>, Error<'gc>> {
let value = match args.get(0).cloned() { let value = match args.get(0).cloned() {
Some(Value::String(s)) => s, Some(Value::String(s)) => s,
Some(v) => Avm1String::new( Some(v) => v.coerce_to_string(activation, ac)?,
ac.gc_context,
v.coerce_to_string(activation, ac)?.to_string(),
),
_ => Avm1String::new(ac.gc_context, String::new()), _ => Avm1String::new(ac.gc_context, String::new()),
}; };
@ -182,13 +179,7 @@ fn char_at<'gc>(
// TODO: Will return REPLACEMENT_CHAR if this indexes a character outside the BMP, losing info about the surrogate. // TODO: Will return REPLACEMENT_CHAR if this indexes a character outside the BMP, losing info about the surrogate.
// When we improve our string representation, the unpaired surrogate should be returned. // When we improve our string representation, the unpaired surrogate should be returned.
let this_val = Value::from(this); let this_val = Value::from(this);
let string = match this_val { let string = this_val.coerce_to_string(activation, context)?;
Value::String(string) => string,
other => Avm1String::new(
context.gc_context,
other.coerce_to_string(activation, context)?.to_string(),
),
};
let i = args let i = args
.get(0) .get(0)
.unwrap_or(&Value::Undefined) .unwrap_or(&Value::Undefined)
@ -406,13 +397,7 @@ fn split<'gc>(
args: &[Value<'gc>], args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> { ) -> Result<Value<'gc>, Error<'gc>> {
let this_val = Value::from(this); let this_val = Value::from(this);
let this = match this_val { let this = this_val.coerce_to_string(activation, context)?;
Value::String(string) => string,
other => Avm1String::new(
context.gc_context,
other.coerce_to_string(activation, context)?.to_string(),
),
};
let delimiter_val = args.get(0).unwrap_or(&Value::Undefined); let delimiter_val = args.get(0).unwrap_or(&Value::Undefined);
let delimiter = delimiter_val.coerce_to_string(activation, context)?; let delimiter = delimiter_val.coerce_to_string(activation, context)?;
let limit = match args.get(1) { let limit = match args.get(1) {
@ -454,13 +439,7 @@ fn substr<'gc>(
} }
let this_val = Value::from(this); let this_val = Value::from(this);
let this = match this_val { let this = this_val.coerce_to_string(activation, context)?;
Value::String(string) => string,
other => Avm1String::new(
context.gc_context,
other.coerce_to_string(activation, context)?.to_string(),
),
};
let this_len = this.encode_utf16().count(); let this_len = this.encode_utf16().count();
let start_index = string_wrapping_index( let start_index = string_wrapping_index(
args.get(0).unwrap().coerce_to_i32(activation, context)?, args.get(0).unwrap().coerce_to_i32(activation, context)?,
@ -487,13 +466,7 @@ fn substring<'gc>(
} }
let this_val = Value::from(this); let this_val = Value::from(this);
let this = match this_val { let this = this_val.coerce_to_string(activation, context)?;
Value::String(string) => string,
other => Avm1String::new(
context.gc_context,
other.coerce_to_string(activation, context)?.to_string(),
),
};
let this_len = this.encode_utf16().count(); let this_len = this.encode_utf16().count();
let mut start_index = string_index( let mut start_index = string_index(
args.get(0).unwrap().coerce_to_i32(activation, context)?, args.get(0).unwrap().coerce_to_i32(activation, context)?,

View File

@ -112,7 +112,7 @@ pub fn set_html_text<'gc>(
.get(0) .get(0)
.unwrap_or(&Value::Undefined) .unwrap_or(&Value::Undefined)
.coerce_to_string(activation, context)?; .coerce_to_string(activation, context)?;
let _ = text_field.set_html_text(text.into_owned(), context); let _ = text_field.set_html_text(text.to_string(), context);
// Changing the htmlText does NOT update variable bindings (does not call EditText::propagate_text_binding). // Changing the htmlText does NOT update variable bindings (does not call EditText::propagate_text_binding).
} }
} }
@ -326,7 +326,7 @@ fn set_variable<'gc>(
.as_display_object() .as_display_object()
.and_then(|dobj| dobj.as_edit_text()) .and_then(|dobj| dobj.as_edit_text())
{ {
etext.set_variable(variable.map(|v| v.into_owned()), activation, context); etext.set_variable(variable.map(|v| v.to_string()), activation, context);
} }
Ok(Value::Undefined) Ok(Value::Undefined)
@ -627,7 +627,7 @@ fn replace_text<'gc>(
.cloned() .cloned()
.unwrap_or(Value::Undefined) .unwrap_or(Value::Undefined)
.coerce_to_string(activation, context)? .coerce_to_string(activation, context)?
.into_owned(); .to_string();
text_field.replace_text(from as usize, to as usize, &text, context); text_field.replace_text(from as usize, to as usize, &text, context);

View File

@ -13,7 +13,6 @@ use crate::xml::{XMLDocument, XMLNode};
use enumset::EnumSet; use enumset::EnumSet;
use gc_arena::MutationContext; use gc_arena::MutationContext;
use quick_xml::Error as ParseError; use quick_xml::Error as ParseError;
use std::borrow::Cow;
pub const XML_NO_ERROR: f64 = 0.0; pub const XML_NO_ERROR: f64 = 0.0;
#[allow(dead_code)] #[allow(dead_code)]
@ -791,7 +790,7 @@ pub fn xml_parse_xml<'gc>(
if let Some(Ok(xmlstring)) = args.get(0).map(|s| s.coerce_to_string(activation, ac)) { if let Some(Ok(xmlstring)) = args.get(0).map(|s| s.coerce_to_string(activation, ac)) {
xmlstring xmlstring
} else { } else {
Cow::Borrowed("") "".into()
}; };
if let Some(children) = node.children() { if let Some(children) = node.children() {

View File

@ -187,7 +187,7 @@ impl<'gc> TObject<'gc> for StageObject<'gc> {
.filter(|binding| binding.variable_name == name) .filter(|binding| binding.variable_name == name)
{ {
let _ = binding.text_field.set_html_text( let _ = binding.text_field.set_html_text(
value.coerce_to_string(activation, context)?.into_owned(), value.coerce_to_string(activation, context)?.to_string(),
context, context,
); );
} }

View File

@ -1,4 +1,5 @@
use gc_arena::{Collect, Gc, MutationContext}; use gc_arena::{Collect, Gc, MutationContext};
use std::fmt;
use std::ops::Deref; use std::ops::Deref;
#[derive(Debug, Clone, Collect)] #[derive(Debug, Clone, Collect)]
@ -26,6 +27,14 @@ impl<'gc> Avm1String<'gc> {
} }
} }
impl Default for Avm1String<'_> {
fn default() -> Self {
Self {
source: Source::Static(""),
}
}
}
impl<'gc> From<&'static str> for Avm1String<'gc> { impl<'gc> From<&'static str> for Avm1String<'gc> {
fn from(str: &'static str) -> Self { fn from(str: &'static str) -> Self {
Self { Self {
@ -34,6 +43,12 @@ impl<'gc> From<&'static str> for Avm1String<'gc> {
} }
} }
impl fmt::Display for Avm1String<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self)
}
}
impl Deref for Avm1String<'_> { impl Deref for Avm1String<'_> {
type Target = str; type Target = str;
@ -56,6 +71,13 @@ impl AsRef<str> for Avm1String<'_> {
} }
} }
impl<'gc> PartialEq<Avm1String<'gc>> for Avm1String<'gc> {
#[inline]
fn eq(&self, other: &Avm1String<'gc>) -> bool {
PartialEq::eq(self.as_str(), other.as_str())
}
}
macro_rules! impl_eq { macro_rules! impl_eq {
($lhs:ty, $rhs: ty) => { ($lhs:ty, $rhs: ty) => {
#[allow(unused_lifetimes)] #[allow(unused_lifetimes)]

View File

@ -457,26 +457,29 @@ impl<'gc> Value<'gc> {
&'a self, &'a self,
activation: &mut Activation<'_, 'gc>, activation: &mut Activation<'_, 'gc>,
context: &mut UpdateContext<'_, 'gc, '_>, context: &mut UpdateContext<'_, 'gc, '_>,
) -> Result<Cow<'a, str>, Error<'gc>> { ) -> Result<Avm1String<'gc>, Error<'gc>> {
Ok(match self { Ok(match self {
Value::Object(object) => { Value::Object(object) => {
match object.call_method("toString", &[], activation, context)? { match object.call_method("toString", &[], activation, context)? {
Value::String(s) => Cow::Owned(s.to_string()), Value::String(s) => s,
_ => Cow::Borrowed("[type Object]"), _ => "[type Object]".into(),
} }
} }
Value::Undefined => { Value::Undefined => {
if activation.current_swf_version() >= 7 { if activation.current_swf_version() >= 7 {
Cow::Borrowed("undefined") "undefined".into()
} else { } else {
Cow::Borrowed("") "".into()
} }
} }
Value::Null => Cow::Borrowed("null"), Value::Null => "null".into(),
Value::Bool(true) => Cow::Borrowed("true"), Value::Bool(true) => "true".into(),
Value::Bool(false) => Cow::Borrowed("false"), Value::Bool(false) => "false".into(),
Value::Number(v) => f64_to_string(*v), Value::Number(v) => match f64_to_string(*v) {
Value::String(v) => Cow::Borrowed(v), Cow::Borrowed(str) => str.into(),
Cow::Owned(str) => Avm1String::new(context.gc_context, str),
},
Value::String(v) => v.to_owned(),
}) })
} }

View File

@ -733,7 +733,7 @@ impl<'gc> EditText<'gc> {
value value
.coerce_to_string(activation, context) .coerce_to_string(activation, context)
.unwrap_or_default() .unwrap_or_default()
.into_owned(), .to_string(),
context, context,
); );
} else { } else {