-
-
Notifications
You must be signed in to change notification settings - Fork 882
avm1: Fix Object < Object comparison #6548
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
The Less2 op returns false if the `valueOf` of either parameter results in an object. The exception is display objects: `mc < mc` returns undefined.
All callers converted the result to `Value` anyway.
Tested the PR and it looks good to me. Though this is not really in the scope of this PR, I've found another issue with
|
let prim_self = self.to_primitive_num(activation)?; | ||
if matches!(prim_self, Value::Object(o) if o.as_display_object().is_none()) { | ||
return Ok(false.into()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems like if both operands are plain objects, then both valueOf
callbacks should be invoked:
var a = { valueOf: function() { trace("in a.valueOf"); } };
var b = { valueOf: function() { trace("in b.valueOf"); } };
trace(a < b); // traces "a.valueOf", "b.valueOf"
trace(b < a); // traces "b.valueOf", "a.valueOf"
If I get it right, Ruffle with this PR would bail-out early and invoke only the first valueOf
.
So I think the correct order is something like this:
a.to_primitive_num()
b.to_primitive_num()
- if
prim_self
is an object, returnfalse
- if
prim_other
is an object, returnfalse
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The PR runs this example correctly -- the valueOfs in this case return undefined
and not an Object, so the early-exit doesn't happen.
The early-exit does happen if you add return {}
to the valueOf calls.
@@ -224,7 +224,9 @@ impl<'gc> Value<'gc> { | |||
activation: &mut Activation<'_, 'gc, '_>, | |||
) -> Result<Value<'gc>, Error<'gc>> { | |||
Ok(match self { | |||
Value::Object(object) => object.call_method("valueOf".into(), &[], activation)?, | |||
Value::Object(object) if object.as_display_object().is_none() => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not directly related to this PR, but I think StageObject
should be a separate enum member of Value
rather than a "subclass" of Value::Object
, since I have seen this special case exclusion too many times. This probably means that StageObject
won't be able to implement TObject
anymore, so it's not trivial at all, but I still have a feeling that StageObject
and Object
are more different than they have in common. Maybe this worth re-thinking as part of #5492.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had the same thought while writing this PR. This would fit nicely in with #5492.
{} < {}
should return false, but was incorrectly returning undefined in Ruffle. Stumbled on this while trying to add a test for #6544.If the parameters to the LessThan2 action are a non-movieclip object,
Object.valueOf
is called. If the result ofvalueOf
is a non-movieclip Object, the comparison immediately returns false. This is the usual case becauseObject.prototype.valueOf
returns this object itself.In the other case where
valueOf
returns a primitive value or a display object, the comparison continues as normal, coercing the value to number, where anyNaN
results in the comparison returning undefined.Value::to_primitive_num
to not callvalueOf
on display objects.abstract_lt
to return false immediately if the result ofto_primitive_num
is an object (excluding display objects).string_coercion
test as this now works properly.lessthan2
tests.String(function)
returns[type Function]
#6544