avm1: don't use &str for activation's path & variable logic

This commit is contained in:
Moulins 2021-09-22 16:51:46 +02:00 committed by kmeisthax
parent 1d9d7e6942
commit 8863b54db0
6 changed files with 83 additions and 114 deletions

View File

@ -11,7 +11,7 @@ use crate::backend::navigator::{NavigationMethod, RequestOptions};
use crate::context::UpdateContext;
use crate::display_object::{DisplayObject, MovieClip, TDisplayObject, TDisplayObjectContainer};
use crate::ecma_conversions::f64_to_wrapping_u32;
use crate::string::{AvmString, BorrowWStr, WString};
use crate::string::{AvmString, BorrowWStr, WStr, WString};
use crate::tag_utils::SwfSlice;
use crate::vminterface::Instantiator;
use crate::{avm_error, avm_warn};
@ -557,7 +557,8 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
Action::SetMember => self.action_set_member(),
Action::SetProperty => self.action_set_property(),
Action::SetTarget(target) => {
self.action_set_target(&target.to_str_lossy(self.encoding()))
let target = WString::from_utf8_owned(target.to_string_lossy(self.encoding()));
self.action_set_target(target.borrow())
}
Action::SetTarget2 => self.action_set_target2(),
Action::SetVariable => self.action_set_variable(),
@ -759,12 +760,13 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
} else {
// An optional path to a MovieClip and a frame #/label, such as "/clip:framelabel".
let frame_path = arg.coerce_to_string(self)?;
if let Some((clip, frame)) = self.resolve_variable_path(target, &frame_path)? {
if let Some((clip, frame)) = self.resolve_variable_path(target, frame_path.borrow())? {
if let Some(clip) = clip.as_display_object().and_then(|o| o.as_movie_clip()) {
let frame = frame.to_utf8_lossy(); // TODO: avoid this UTF8 conversion.
if let Ok(frame) = frame.parse().map(f64_to_wrapping_u32) {
// First try to parse as a frame number.
call_frame = Some((clip, frame));
} else if let Some(frame) = clip.frame_label_to_number(frame) {
} else if let Some(frame) = clip.frame_label_to_number(&frame) {
// Otherwise, it's a frame label.
call_frame = Some((clip, frame.into()));
}
@ -1908,7 +1910,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
Ok(FrameControl::Continue)
}
fn action_set_target(&mut self, target: &str) -> Result<FrameControl<'gc>, Error<'gc>> {
fn action_set_target(&mut self, target: WStr<'_>) -> Result<FrameControl<'gc>, Error<'gc>> {
let base_clip = self.base_clip();
let new_target_clip;
let root = base_clip.avm1_root(&self.context)?;
@ -1925,13 +1927,13 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
} else {
avm_warn!(self, "SetTarget failed: {} not found", target);
// TODO: Emulate AVM1 trace error message.
let path = if base_clip.removed() { None } else { Some(base_clip.path()) };
let message = format!(
"Target not found: Target=\"{}\" Base=\"{}\"",
target,
if base_clip.removed() {
"?".to_string()
} else {
base_clip.path()
match &path {
Some(p) => p.borrow(),
None => WStr::from_units(b"?"),
}
);
self.context.log.avm_trace(&message);
@ -1966,7 +1968,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
match target {
Value::String(target) => {
return self.action_set_target(&target);
return self.action_set_target(target.borrow());
}
Value::Undefined => {
// Reset.
@ -1980,12 +1982,12 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
} else {
// Other objects get coerced to string.
let target = target.coerce_to_string(self)?;
return self.action_set_target(&target);
return self.action_set_target(target.borrow());
}
}
_ => {
let target = target.coerce_to_string(self)?;
return self.action_set_target(&target);
return self.action_set_target(target.borrow());
}
};
@ -2151,7 +2153,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
let param = self.context.avm1.pop().coerce_to_object(self);
let result = if let Some(display_object) = param.as_display_object() {
let path = display_object.path();
AvmString::new(self.context.gc_context, path).into()
AvmString::new_ucs2(self.context.gc_context, path).into()
} else {
Value::Undefined
};
@ -2504,7 +2506,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
let root = start.avm1_root(&self.context)?;
let start = start.object().coerce_to_object(self);
Ok(self
.resolve_target_path(root, start, &path, false)?
.resolve_target_path(root, start, path.borrow(), false)?
.and_then(|o| o.as_display_object()))
}
@ -2521,8 +2523,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
&mut self,
root: DisplayObject<'gc>,
start: Object<'gc>,
// TODO(moulins): replace by Str<'_> once the API is good enough.
path: &str,
mut path: WStr<'_>,
mut first_element: bool,
) -> Result<Option<Object<'gc>>, Error<'gc>> {
// Empty path resolves immediately to start clip.
@ -2532,9 +2533,8 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
// Starting / means an absolute path starting from root.
// (`/bar` means `_root.bar`)
let mut path = path.as_bytes();
let (mut object, mut is_slash_path) = if path[0] == b'/' {
path = &path[1..];
let (mut object, mut is_slash_path) = if path.starts_with(b'/') {
path = path.slice(1..);
(root.object().coerce_to_object(self), true)
} else {
(start, false)
@ -2546,17 +2546,19 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
while !path.is_empty() {
// Skip any number of leading :
// `foo`, `:foo`, and `:::foo` are all the same
while path.get(0) == Some(&b':') {
path = &path[1..];
}
path = path.trim_start_matches(b':');
let val = if let b".." | b"../" | b"..:" = &path[..std::cmp::min(path.len(), 3)] {
let prefix = path.slice(..path.len().min(3));
let val = if prefix == b".."
|| prefix == b"../"
|| prefix == b"..:"
{
// Check for ..
// SWF-4 style _parent
if path.get(2) == Some(&b'/') {
if path.try_get(2) == Some(u16::from(b'/')) {
is_slash_path = true;
}
path = path.get(3..).unwrap_or(&[]);
path = path.try_slice(3..).unwrap_or_default();
if let Some(parent) = object.as_display_object().and_then(|o| o.avm1_parent()) {
parent.object()
} else {
@ -2571,10 +2573,10 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
// TODO: SWF4 is probably more restrictive.
let mut pos = 0;
while pos < path.len() {
match path[pos] {
b':' => break,
b'.' if !is_slash_path => break,
b'/' => {
match u8::try_from(path.get(pos)) {
Ok(b':') => break,
Ok(b'.') if !is_slash_path => break,
Ok(b'/') => {
is_slash_path = true;
break;
}
@ -2584,15 +2586,12 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
}
// Slice out the identifier and step the cursor past the delimiter.
let ident = &path[..pos];
path = path.get(pos + 1..).unwrap_or(&[]);
let name = path.slice(..pos);
path = path.try_slice(pos + 1..).unwrap_or_default();
// Guaranteed to be valid UTF-8.
let name = unsafe { std::str::from_utf8_unchecked(ident) };
if first_element && name == "this" {
if first_element && name == b"this" {
self.this_cell().into()
} else if first_element && name == "_root" {
} else if first_element && name == b"_root" {
self.root_object()?
} else {
// Get the value from the object.
@ -2601,13 +2600,11 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
if let Some(child) = object
.as_display_object()
.and_then(|o| o.as_container())
.and_then(|o| {
o.child_by_name(WString::from_utf8(name).borrow(), case_sensitive)
})
.and_then(|o| o.child_by_name(name, case_sensitive))
{
child.object()
} else {
let name = AvmString::new(self.context.gc_context, name);
let name = AvmString::new_ucs2(self.context.gc_context, name.into());
object.get(name, self).unwrap()
}
}
@ -2634,29 +2631,14 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
pub fn resolve_variable_path<'s>(
&mut self,
start: DisplayObject<'gc>,
// TODO(moulins): replace by Str<'_> once the API is good enough.
path: &'s str,
) -> Result<Option<(Object<'gc>, &'s str)>, Error<'gc>> {
path: WStr<'s>,
) -> Result<Option<(Object<'gc>, WStr<'s>)>, Error<'gc>> {
// Find the right-most : or . in the path.
// If we have one, we must resolve as a target path.
// We also check for a / to skip some unnecessary work later.
let mut has_slash = false;
let mut var_iter = path.as_bytes().rsplitn(2, |c| match c {
b':' | b'.' => true,
b'/' => {
has_slash = true;
false
}
_ => false,
});
let b = var_iter.next();
let a = var_iter.next();
if let (Some(path), Some(var_name)) = (a, b) {
if let Some(separator) = path.rfind(b":.".as_ref()) {
// We have a . or :, so this is a path to an object plus a variable name.
// We resolve it directly on the targeted object.
let path = unsafe { std::str::from_utf8_unchecked(path) };
let var_name = unsafe { std::str::from_utf8_unchecked(var_name) };
let (path, var_name) = (path.slice(..separator), path.slice(separator + 1..));
let mut current_scope = Some(self.scope_cell());
while let Some(scope) = current_scope {
@ -2708,24 +2690,10 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
// Find the right-most : or . in the path.
// If we have one, we must resolve as a target path.
// We also check for a / to skip some unnecessary work later.
let mut has_slash = false;
let mut var_iter = path.as_bytes().rsplitn(2, |c| match c {
b':' | b'.' => true,
b'/' => {
has_slash = true;
false
}
_ => false,
});
let b = var_iter.next();
let a = var_iter.next();
if let (Some(path), Some(var_name)) = (a, b) {
if let Some(separator) = path.rfind(b":.".as_ref()) {
// We have a . or :, so this is a path to an object plus a variable name.
// We resolve it directly on the targeted object.
let path = unsafe { std::str::from_utf8_unchecked(path) };
let var_name = unsafe { std::str::from_utf8_unchecked(var_name) };
let (path, var_name) = (path.slice(..separator), path.slice(separator + 1..));
let mut current_scope = Some(self.scope_cell());
while let Some(scope) = current_scope {
@ -2733,7 +2701,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
if let Some(object) =
self.resolve_target_path(avm1_root, *scope.read().locals(), path, true)?
{
let var_name = AvmString::new(self.context.gc_context, var_name);
let var_name = AvmString::new_ucs2(self.context.gc_context, var_name.into());
if object.has_property(self, var_name) {
return Ok(CallableValue::Callable(object, object.get(var_name, self)?));
}
@ -2745,14 +2713,16 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
}
// If it doesn't have a trailing variable, it can still be a slash path.
// We can skip this step if we didn't find a slash above.
if has_slash {
if path.contains(b'/') {
let mut current_scope = Some(self.scope_cell());
while let Some(scope) = current_scope {
let avm1_root = start.avm1_root(&self.context)?;
if let Some(object) =
self.resolve_target_path(avm1_root, *scope.read().locals(), &path, false)?
{
if let Some(object) = self.resolve_target_path(
avm1_root,
*scope.read().locals(),
path.borrow(),
false,
)? {
return Ok(CallableValue::UnCallable(object.into()));
}
current_scope = scope.read().parent_cell();
@ -2801,16 +2771,12 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
// Find the right-most : or . in the path.
// If we have one, we must resolve as a target path.
let mut var_iter = path.as_bytes().rsplitn(2, |&c| c == b':' || c == b'.');
let b = var_iter.next();
let a = var_iter.next();
let separator = path.rfind(b":.".as_ref());
if let (Some(path), Some(var_name)) = (a, b) {
if let Some(sep) = separator {
// We have a . or :, so this is a path to an object plus a variable name.
// We resolve it directly on the targeted object.
let path = unsafe { std::str::from_utf8_unchecked(path) };
let var_name = unsafe { std::str::from_utf8_unchecked(var_name) };
let var_name = AvmString::new(self.context.gc_context, var_name);
let (path, var_name) = (path.slice(..sep), path.slice(sep + 1..));
let mut current_scope = Some(self.scope_cell());
while let Some(scope) = current_scope {
@ -2818,6 +2784,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
if let Some(object) =
self.resolve_target_path(avm1_root, *scope.read().locals(), path, true)?
{
let var_name = AvmString::new_ucs2(self.context.gc_context, var_name.into());
object.set(var_name, value, self)?;
return Ok(());
}

View File

@ -89,7 +89,7 @@ pub fn to_string<'gc>(
_args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> {
if let Some(display_object) = this.as_display_object() {
Ok(AvmString::new(activation.context.gc_context, display_object.path()).into())
Ok(AvmString::new_ucs2(activation.context.gc_context, display_object.path()).into())
} else {
Ok(Value::Undefined)
}

View File

@ -1004,7 +1004,7 @@ pub fn goto_frame<'gc>(
// This can direct other clips than the one this method was called on!
let frame_path = val.coerce_to_string(activation)?;
if let Some((clip, frame)) =
activation.resolve_variable_path(movie_clip.into(), &frame_path)?
activation.resolve_variable_path(movie_clip.into(), frame_path.borrow())?
{
if let Some(clip) = clip.as_display_object().and_then(|o| o.as_movie_clip()) {
// TODO(moulins): we need WStr::parse for avoiding allocation here.

View File

@ -782,7 +782,7 @@ fn set_rotation<'gc>(
}
fn target<'gc>(activation: &mut Activation<'_, 'gc, '_>, this: DisplayObject<'gc>) -> Value<'gc> {
AvmString::new(activation.context.gc_context, this.slash_path()).into()
AvmString::new_ucs2(activation.context.gc_context, this.slash_path()).into()
}
fn frames_loaded<'gc>(
@ -794,8 +794,8 @@ fn frames_loaded<'gc>(
.map_or(Value::Undefined, Value::from)
}
fn name<'gc>(activation: &mut Activation<'_, 'gc, '_>, this: DisplayObject<'gc>) -> Value<'gc> {
AvmString::new(activation.context.gc_context, this.name().to_string()).into()
fn name<'gc>(_activation: &mut Activation<'_, 'gc, '_>, this: DisplayObject<'gc>) -> Value<'gc> {
this.name().into()
}
fn set_name<'gc>(
@ -817,7 +817,7 @@ fn drop_target<'gc>(
.map_or_else(
|| "".into(),
|drop_target| {
AvmString::new(activation.context.gc_context, drop_target.slash_path()).into()
AvmString::new_ucs2(activation.context.gc_context, drop_target.slash_path()).into()
},
)
}

View File

@ -10,7 +10,7 @@ use crate::context::{RenderContext, UpdateContext};
use crate::drawing::Drawing;
use crate::player::NEWEST_PLAYER_VERSION;
use crate::prelude::*;
use crate::string::AvmString;
use crate::string::{AvmString, BorrowWStr, WString};
use crate::tag_utils::SwfMovie;
use crate::transform::Transform;
use crate::types::{Degrees, Percent};
@ -789,34 +789,34 @@ pub trait TDisplayObject<'gc>:
}
/// Returns the dot-syntax path to this display object, e.g. `_level0.foo.clip`
fn path(&self) -> String {
fn path(&self) -> WString {
if let Some(parent) = self.avm1_parent() {
let mut path = parent.path();
path.push('.');
path.push_str(&*self.name());
path.push_byte(b'.');
path.push_str(self.name().borrow());
path
} else {
format!("_level{}", self.depth())
WString::from_utf8_owned(format!("_level{}", self.depth()))
}
}
/// Returns the Flash 4 slash-syntax path to this display object, e.g. `/foo/clip`.
/// Returned by the `_target` property in AVM1.
fn slash_path(&self) -> String {
fn build_slash_path(object: DisplayObject<'_>) -> String {
fn slash_path(&self) -> WString {
fn build_slash_path(object: DisplayObject<'_>) -> WString {
if let Some(parent) = object.avm1_parent() {
let mut path = build_slash_path(parent);
path.push('/');
path.push_str(&*object.name());
path.push_byte(b'/');
path.push_str(object.name().borrow());
path
} else {
let level = object.depth();
if level == 0 {
// _level0 does not append its name in slash syntax.
String::new()
WString::new()
} else {
// Other levels do append their name.
format!("_level{}", level)
WString::from_utf8_owned(format!("_level{}", level))
}
}
}
@ -825,7 +825,7 @@ pub trait TDisplayObject<'gc>:
build_slash_path((*self).into())
} else {
// _target of _level0 should just be '/'.
'/'.to_string()
WString::from_unit(b'/'.into())
}
}

View File

@ -20,7 +20,7 @@ use crate::font::{round_down_to_pixel, Glyph, TextRenderSettings};
use crate::html::{BoxBounds, FormatSpans, LayoutBox, LayoutContent, TextFormat};
use crate::prelude::*;
use crate::shape_utils::DrawCommand;
use crate::string::{utils as string_utils, AvmString};
use crate::string::{utils as string_utils, AvmString, BorrowWStr, WString};
use crate::tag_utils::SwfMovie;
use crate::transform::Transform;
use crate::vminterface::{AvmObject, AvmType, Instantiator};
@ -1047,7 +1047,7 @@ impl<'gc> EditText<'gc> {
// Avoid double-borrows by copying the string.
// TODO: Can we avoid this somehow? Maybe when we have a better string type.
let variable = (*var_path).to_string();
let variable_path = WString::from_utf8(&var_path);
drop(var_path);
let parent = self.avm1_parent().unwrap();
@ -1058,9 +1058,10 @@ impl<'gc> EditText<'gc> {
activation.context.swf.version(),
|activation| {
if let Ok(Some((object, property))) =
activation.resolve_variable_path(parent, &variable)
activation.resolve_variable_path(parent, variable_path.borrow())
{
let property = AvmString::new(activation.context.gc_context, property);
let property =
AvmString::new_ucs2(activation.context.gc_context, property.into());
// If this text field was just created, we immediately propagate the text to the variable (or vice versa).
if set_initial_value {
@ -1124,11 +1125,11 @@ impl<'gc> EditText<'gc> {
if let Some(variable) = self.variable() {
// Avoid double-borrows by copying the string.
// TODO: Can we avoid this somehow? Maybe when we have a better string type.
let variable_path = variable.to_string();
let variable_path = WString::from_utf8(&variable);
drop(variable);
if let Ok(Some((object, property))) =
activation.resolve_variable_path(self.avm1_parent().unwrap(), &variable_path)
if let Ok(Some((object, property))) = activation
.resolve_variable_path(self.avm1_parent().unwrap(), variable_path.borrow())
{
let html_text = self.html_text(&mut activation.context);
@ -1139,7 +1140,8 @@ impl<'gc> EditText<'gc> {
self.avm1_parent().unwrap(),
activation.context.swf.version(),
|activation| {
let property = AvmString::new(activation.context.gc_context, property);
let property =
AvmString::new_ucs2(activation.context.gc_context, property.into());
let _ = object.set(
property,
AvmString::new(activation.context.gc_context, html_text).into(),