core: Trying to access a named shape should resolve to the parent as they have no object representation

This commit is contained in:
CUB3D 2023-07-14 21:30:32 +01:00 committed by Nathan Adams
parent 4c1c3cc105
commit 771f568509
15 changed files with 133 additions and 57 deletions

View File

@ -1127,7 +1127,7 @@ impl<'a, 'gc> Activation<'a, 'gc> {
let object_val = self.context.avm1.pop();
let object = object_val.coerce_to_object(self);
let result = object.get(name, self)?;
let result = object.get_non_slash_path(name, self)?;
self.stack_push(result);
Ok(FrameControl::Continue)
@ -2563,7 +2563,7 @@ impl<'a, 'gc> Activation<'a, 'gc> {
let root = start.avm1_root();
let start = start.object().coerce_to_object(self);
Ok(self
.resolve_target_path(root, start, &path, false)?
.resolve_target_path(root, start, &path, false, true)?
.and_then(|o| o.as_display_object()))
}
@ -2582,6 +2582,7 @@ impl<'a, 'gc> Activation<'a, 'gc> {
start: Object<'gc>,
mut path: &WStr,
mut first_element: bool,
path_has_slash: bool,
) -> Result<Option<Object<'gc>>, Error<'gc>> {
// Empty path resolves immediately to start clip.
if path.is_empty() {
@ -2656,10 +2657,25 @@ impl<'a, 'gc> Activation<'a, 'gc> {
.and_then(|o| o.as_container())
.and_then(|o| o.child_by_name(name, case_sensitive))
{
// If an object doesn't have an object representation, e.g. Graphic, then trying to access it
// Returns the parent instead
if path_has_slash {
child.object()
} else if let crate::display_object::DisplayObject::Graphic(_) = child {
child
.parent()
.map(|p| p.object())
.unwrap_or(Value::Undefined)
} else {
child.object()
}
} else {
let name = AvmString::new(self.context.gc_context, name);
if path_has_slash {
object.get(name, self).unwrap()
} else {
object.get_non_slash_path(name, self).unwrap()
}
}
}
};
@ -2689,6 +2705,8 @@ impl<'a, 'gc> Activation<'a, 'gc> {
start: DisplayObject<'gc>,
path: &'s WStr,
) -> Result<Option<(Object<'gc>, &'s WStr)>, Error<'gc>> {
let path_has_slash = path.contains(b'/');
// Find the right-most : or . in the path.
// If we have one, we must resolve as a target path.
if let Some(separator) = path.rfind(b":.".as_ref()) {
@ -2699,9 +2717,13 @@ impl<'a, 'gc> Activation<'a, 'gc> {
let mut current_scope = Some(self.scope());
while let Some(scope) = current_scope {
let avm1_root = start.avm1_root();
if let Some(object) =
self.resolve_target_path(avm1_root, *scope.locals(), path, true)?
{
if let Some(object) = self.resolve_target_path(
avm1_root,
*scope.locals(),
path,
true,
path_has_slash,
)? {
return Ok(Some((object, var_name)));
}
current_scope = scope.parent();
@ -2744,6 +2766,8 @@ impl<'a, 'gc> Activation<'a, 'gc> {
// Resolve a variable path for a GetVariable action.
let start = self.target_clip_or_root();
let path_has_slash = path.contains(b'/');
// Find the right-most : or . in the path.
// If we have one, we must resolve as a target path.
if let Some(separator) = path.rfind(b":.".as_ref()) {
@ -2754,9 +2778,13 @@ impl<'a, 'gc> Activation<'a, 'gc> {
let mut current_scope = Some(self.scope());
while let Some(scope) = current_scope {
let avm1_root = start.avm1_root();
if let Some(object) =
self.resolve_target_path(avm1_root, *scope.locals(), path, true)?
{
if let Some(object) = self.resolve_target_path(
avm1_root,
*scope.locals(),
path,
true,
path_has_slash,
)? {
let var_name = AvmString::new(self.context.gc_context, var_name);
if object.has_property(self, var_name) {
return Ok(CallableValue::Callable(object, object.get(var_name, self)?));
@ -2774,7 +2802,7 @@ impl<'a, 'gc> Activation<'a, 'gc> {
while let Some(scope) = current_scope {
let avm1_root = start.avm1_root();
if let Some(object) =
self.resolve_target_path(avm1_root, *scope.locals(), &path, false)?
self.resolve_target_path(avm1_root, *scope.locals(), &path, false, true)?
{
return Ok(CallableValue::UnCallable(object.into()));
}
@ -2841,7 +2869,7 @@ impl<'a, 'gc> Activation<'a, 'gc> {
while let Some(scope) = current_scope {
let avm1_root = start.avm1_root();
if let Some(object) =
self.resolve_target_path(avm1_root, *scope.locals(), path, true)?
self.resolve_target_path(avm1_root, *scope.locals(), path, true, true)?
{
let var_name = AvmString::new(self.context.gc_context, var_name);
object.set(var_name, value, self)?;
@ -3075,7 +3103,7 @@ impl<'a, 'gc> Activation<'a, 'gc> {
if target.is_empty() {
new_target_clip = Some(base_clip);
} else if let Some(clip) = self
.resolve_target_path(root, start, target, false)?
.resolve_target_path(root, start, target, false, true)?
.and_then(|o| o.as_display_object())
.filter(|_| !self.base_clip.avm1_removed())
// All properties invalid if base clip is removed.

View File

@ -582,10 +582,10 @@ fn sort_on_compare<'a, 'gc>(fields: &'a [(AvmString<'gc>, SortOptions)]) -> Comp
if let [Value::Object(a), Value::Object(b)] = [a, b] {
for (field_name, options) in fields {
let a_prop = a
.get_local_stored(*field_name, activation)
.get_local_stored(*field_name, activation, false)
.unwrap_or(Value::Undefined);
let b_prop = b
.get_local_stored(*field_name, activation)
.get_local_stored(*field_name, activation, false)
.unwrap_or(Value::Undefined);
let result = sort_compare(activation, &a_prop, &b_prop, *options)?;

View File

@ -391,7 +391,7 @@ fn clone<'gc>(
if !bitmap_data.disposed() {
return Ok(new_bitmap_data(
activation.context.gc_context,
this.get_local_stored("__proto__", activation),
this.get_local_stored("__proto__", activation, false),
operations::clone(bitmap_data),
)
.into());
@ -756,8 +756,8 @@ fn hit_test<'gc>(
.unwrap_or(&Value::Undefined)
.coerce_to_object(activation);
let top_left = if let (Some(x), Some(y)) = (
first_point.get_local_stored("x", activation),
first_point.get_local_stored("y", activation),
first_point.get_local_stored("x", activation, false),
first_point.get_local_stored("y", activation, false),
) {
(x.coerce_to_i32(activation)?, y.coerce_to_i32(activation)?)
} else {
@ -787,8 +787,8 @@ fn hit_test<'gc>(
.unwrap_or(&Value::Undefined)
.coerce_to_object(activation);
let second_point = if let (Some(x), Some(y)) = (
second_point.get_local_stored("x", activation),
second_point.get_local_stored("y", activation),
second_point.get_local_stored("x", activation, false),
second_point.get_local_stored("y", activation, false),
) {
(x.coerce_to_i32(activation)?, y.coerce_to_i32(activation)?)
} else {
@ -814,10 +814,10 @@ fn hit_test<'gc>(
// Determine what kind of Object we have, point or rectangle.
// Duck-typed dumb objects are allowed.
let compare_fields = (
compare_object.get_local_stored("x", activation),
compare_object.get_local_stored("y", activation),
compare_object.get_local_stored("width", activation),
compare_object.get_local_stored("height", activation),
compare_object.get_local_stored("x", activation, false),
compare_object.get_local_stored("y", activation, false),
compare_object.get_local_stored("width", activation, false),
compare_object.get_local_stored("height", activation, false),
);
match compare_fields {
// BitmapData vs. point
@ -1144,10 +1144,10 @@ fn pixel_dissolve<'gc>(
.coerce_to_object(activation);
let (src_min_x, src_min_y, src_width, src_height) =
if let (Some(x), Some(y), Some(width), Some(height)) = (
source_rect.get_local_stored("x", activation),
source_rect.get_local_stored("y", activation),
source_rect.get_local_stored("width", activation),
source_rect.get_local_stored("height", activation),
source_rect.get_local_stored("x", activation, false),
source_rect.get_local_stored("y", activation, false),
source_rect.get_local_stored("width", activation, false),
source_rect.get_local_stored("height", activation, false),
) {
(
x.coerce_to_f64(activation)? as i32,
@ -1365,7 +1365,7 @@ fn compare<'gc>(
match operations::compare(this_bitmap_data, other_bitmap_data) {
Some(bitmap_data) => Ok(new_bitmap_data(
activation.context.gc_context,
this.get_local_stored("__proto__", activation),
this.get_local_stored("__proto__", activation, false),
bitmap_data,
)
.into()),
@ -1406,7 +1406,7 @@ fn load_bitmap<'gc>(
);
Ok(new_bitmap_data(
activation.context.gc_context,
this.get_local_stored("prototype", activation),
this.get_local_stored("prototype", activation, false),
bitmap_data,
)
.into())

View File

@ -67,7 +67,7 @@ pub fn clone<'gc>(
),
_ => return Ok(Value::Undefined),
};
let proto = this.get_local_stored("__proto__", activation);
let proto = this.get_local_stored("__proto__", activation, false);
Ok(create_instance(activation, native, proto).into())
}

View File

@ -177,9 +177,9 @@ impl<'gc> DisplacementMapFilter<'gc> {
value: Option<&Value<'gc>>,
) -> Result<(), Error<'gc>> {
if let Some(Value::Object(object)) = value {
if let Some(x) = object.get_local_stored("x", activation) {
if let Some(x) = object.get_local_stored("x", activation, false) {
let x = x.coerce_to_f64(activation)?.clamp_to_i32();
if let Some(y) = object.get_local_stored("y", activation) {
if let Some(y) = object.get_local_stored("y", activation, false) {
let y = y.coerce_to_f64(activation)?.clamp_to_i32();
self.0.write(activation.context.gc_context).map_point = Point::new(x, y);
}

View File

@ -114,12 +114,12 @@ pub fn object_to_matrix_or_default<'gc>(
) -> Result<Matrix, Error<'gc>> {
if let (Some(a), Some(b), Some(c), Some(d), Some(tx), Some(ty)) = (
// These lookups do not search the prototype chain and ignore virtual properties.
object.get_local_stored("a", activation),
object.get_local_stored("b", activation),
object.get_local_stored("c", activation),
object.get_local_stored("d", activation),
object.get_local_stored("tx", activation),
object.get_local_stored("ty", activation),
object.get_local_stored("a", activation, false),
object.get_local_stored("b", activation, false),
object.get_local_stored("c", activation, false),
object.get_local_stored("d", activation, false),
object.get_local_stored("tx", activation, false),
object.get_local_stored("ty", activation, false),
) {
let a = a.coerce_to_f64(activation)? as f32;
let b = b.coerce_to_f64(activation)? as f32;

View File

@ -148,7 +148,7 @@ fn object_to_rectangle<'gc>(
const NAMES: &[&str] = &["x", "y", "width", "height"];
let mut values = [0; 4];
for (&name, value) in NAMES.iter().zip(&mut values) {
*value = match object.get_local_stored(name, activation) {
*value = match object.get_local_stored(name, activation, false) {
Some(value) => value.coerce_to_i32(activation)?,
None => return Ok(None),
}
@ -1371,10 +1371,10 @@ fn local_to_global<'gc>(
// It does not search the prototype chain and ignores virtual properties.
if let (Value::Number(x), Value::Number(y)) = (
point
.get_local_stored("x", activation)
.get_local_stored("x", activation, false)
.unwrap_or(Value::Undefined),
point
.get_local_stored("y", activation)
.get_local_stored("y", activation, false)
.unwrap_or(Value::Undefined),
) {
let local = Point::from_pixels(x, y);
@ -1537,10 +1537,10 @@ fn global_to_local<'gc>(
// It does not search the prototype chain and ignores virtual properties.
if let (Value::Number(x), Value::Number(y)) = (
point
.get_local_stored("x", activation)
.get_local_stored("x", activation, false)
.unwrap_or(Value::Undefined),
point
.get_local_stored("y", activation)
.get_local_stored("y", activation, false)
.unwrap_or(Value::Undefined),
) {
let global = Point::from_pixels(x, y);

View File

@ -89,8 +89,28 @@ pub trait TObject<'gc>: 'gc + Collect + Into<Object<'gc>> + Clone + Copy {
&self,
name: impl Into<AvmString<'gc>>,
activation: &mut Activation<'_, 'gc>,
is_slash_path: bool,
) -> Option<Value<'gc>> {
self.raw_script_object().get_local_stored(name, activation)
self.raw_script_object()
.get_local_stored(name, activation, is_slash_path)
}
/// Retrieve a named property from the object, or its prototype.
fn get_non_slash_path(
&self,
name: impl Into<AvmString<'gc>>,
activation: &mut Activation<'_, 'gc>,
) -> Result<Value<'gc>, Error<'gc>> {
// TODO: Extract logic to a `lookup` function.
let (this, proto) = if let Some(super_object) = self.as_super_object() {
(super_object.this(), super_object.proto(activation))
} else {
((*self).into(), Value::Object((*self).into()))
};
match search_prototype(proto, name.into(), activation, this, false)? {
Some((value, _depth)) => Ok(value),
None => Ok(Value::Undefined),
}
}
/// Retrieve a named property from the object, or its prototype.
@ -105,7 +125,7 @@ pub trait TObject<'gc>: 'gc + Collect + Into<Object<'gc>> + Clone + Copy {
} else {
((*self).into(), Value::Object((*self).into()))
};
match search_prototype(proto, name.into(), activation, this)? {
match search_prototype(proto, name.into(), activation, this, true)? {
Some((value, _depth)) => Ok(value),
None => Ok(Value::Undefined),
}
@ -127,7 +147,7 @@ pub trait TObject<'gc>: 'gc + Collect + Into<Object<'gc>> + Clone + Copy {
return Err(Error::PrototypeRecursionLimit);
}
if let Some(value) = p.get_local_stored(name, activation) {
if let Some(value) = p.get_local_stored(name, activation, true) {
return Ok(value);
}
@ -257,7 +277,8 @@ pub trait TObject<'gc>: 'gc + Collect + Into<Object<'gc>> + Clone + Copy {
}
}
let (method, depth) = match search_prototype(Value::Object(this), name, activation, this)? {
let (method, depth) =
match search_prototype(Value::Object(this), name, activation, this, false)? {
Some((Value::Object(method), depth)) => (method, depth),
_ => return Ok(Value::Undefined),
};
@ -669,6 +690,7 @@ pub fn search_prototype<'gc>(
name: AvmString<'gc>,
activation: &mut Activation<'_, 'gc>,
this: Object<'gc>,
is_slash_path: bool,
) -> Result<Option<(Value<'gc>, u8)>, Error<'gc>> {
let mut depth = 0;
@ -697,7 +719,7 @@ pub fn search_prototype<'gc>(
}
}
if let Some(value) = p.get_local_stored(name, activation) {
if let Some(value) = p.get_local_stored(name, activation, is_slash_path) {
return Ok(Some((value, depth)));
}

View File

@ -155,6 +155,7 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> {
&self,
name: impl Into<AvmString<'gc>>,
activation: &mut Activation<'_, 'gc>,
_is_slash_path: bool,
) -> Option<Value<'gc>> {
self.0
.read()

View File

@ -193,13 +193,14 @@ impl<'gc> TObject<'gc> for StageObject<'gc> {
&self,
name: impl Into<AvmString<'gc>>,
activation: &mut Activation<'_, 'gc>,
is_slash_path: bool,
) -> Option<Value<'gc>> {
let name = name.into();
let obj = self.0.read();
// Property search order for DisplayObjects:
// 1) Actual properties on the underlying object
if let Some(value) = obj.base.get_local_stored(name, activation) {
if let Some(value) = obj.base.get_local_stored(name, activation, is_slash_path) {
return Some(value);
}
@ -217,7 +218,15 @@ impl<'gc> TObject<'gc> for StageObject<'gc> {
.as_container()
.and_then(|o| o.child_by_name(&name, activation.is_case_sensitive()))
{
return Some(child.object());
return if is_slash_path {
Some(child.object())
// If an object doesn't have an object representation, e.g. Graphic, then trying to access it
// Returns the parent instead
} else if let crate::display_object::DisplayObject::Graphic(_) = child {
child.parent().map(|p| p.object())
} else {
Some(child.object())
};
}
// 4) Display object properties such as `_x`, `_y` (never case sensitive)

View File

@ -75,6 +75,7 @@ impl<'gc> TObject<'gc> for SuperObject<'gc> {
&self,
_name: impl Into<AvmString<'gc>>,
_activation: &mut Activation<'_, 'gc>,
_is_slash_path: bool,
) -> Option<Value<'gc>> {
None
}
@ -124,7 +125,7 @@ impl<'gc> TObject<'gc> for SuperObject<'gc> {
) -> Result<Value<'gc>, Error<'gc>> {
let this = self.0.this;
let (method, depth) =
match search_prototype(self.proto(activation), name, activation, this)? {
match search_prototype(self.proto(activation), name, activation, this, false)? {
Some((Value::Object(method), depth)) => (method, depth),
_ => return Ok(Value::Undefined),
};

View File

@ -140,7 +140,7 @@ impl<'gc> Scope<'gc> {
if self.locals().has_property(activation, name) {
return self
.locals()
.get(name, activation)
.get_non_slash_path(name, activation)
.map(|v| CallableValue::Callable(self.locals_cell(), v));
}
if let Some(scope) = self.parent() {
@ -155,7 +155,7 @@ impl<'gc> Scope<'gc> {
return activation
.root_object()
.coerce_to_object(activation)
.get(name, activation)
.get_non_slash_path(name, activation)
.map(|v| CallableValue::Callable(self.locals_cell(), v));
}
}

View File

@ -0,0 +1,14 @@
root: _level0
_root.foo: foo1
_root.real: _level0.real
_root.real.foo: foo2
get(root.real.foo): foo2
get(root.real/foo): undefined
get(root.real:foo): foo2
_root.empty: _level0
_root.empty.foo: foo1
get(root.empty.foo): foo1
get(root.empty/foo): undefined
get(root.empty:foo): foo1
_root.nothere: undefined
_root.nothere.foo: undefined

Binary file not shown.

View File

@ -0,0 +1 @@
num_frames = 1