avm2: Fix avmplus.describeTypeJSON bugs and add test

This commit is contained in:
Aaron Hill 2023-09-08 12:21:52 -04:00
parent 34fb2e4f48
commit 981dedafd2
11 changed files with 514 additions and 28 deletions

View File

@ -7,6 +7,7 @@ use crate::avm2::property::Property;
use crate::avm2::ClassObject;
use crate::avm2::{Activation, Error, Object, Value};
use crate::avm2_stub_method;
// Implements `avmplus.describeTypeJSON`
pub fn describe_type_json<'gc>(
@ -54,12 +55,17 @@ pub fn describe_type_json<'gc>(
object.set_public_property("isStatic", is_static.into(), activation)?;
let traits = describe_internal_body(activation, class_obj, is_static, flags)?;
object.set_public_property("traits", traits.into(), activation)?;
if flags.contains(DescribeTypeFlags::INCLUDE_TRAITS) {
object.set_public_property("traits", traits.into(), activation)?;
} else {
object.set_public_property("traits", Value::Null, activation)?;
}
Ok(object.into())
}
bitflags::bitflags! {
#[derive(Copy, Clone)]
pub struct DescribeTypeFlags: u32 {
const HIDE_NSURI_METHODS = 1 << 0;
const INCLUDE_BASES = 1 << 1;
@ -92,20 +98,40 @@ fn describe_internal_body<'gc>(
.construct(activation, &[])?;
let bases = ArrayObject::empty(activation)?.as_array_object().unwrap();
let interfaces = ArrayObject::empty(activation)?.as_array_object().unwrap();
let variables = ArrayObject::empty(activation)?.as_array_object().unwrap();
let accessors = ArrayObject::empty(activation)?.as_array_object().unwrap();
let methods = ArrayObject::empty(activation)?.as_array_object().unwrap();
traits.set_public_property("bases", bases.into(), activation)?;
traits.set_public_property("interfaces", interfaces.into(), activation)?;
traits.set_public_property("variables", variables.into(), activation)?;
traits.set_public_property("accessors", accessors.into(), activation)?;
traits.set_public_property("methods", methods.into(), activation)?;
if flags.contains(DescribeTypeFlags::INCLUDE_BASES) {
traits.set_public_property("bases", bases.into(), activation)?;
} else {
traits.set_public_property("bases", Value::Null, activation)?;
}
if flags.contains(DescribeTypeFlags::INCLUDE_INTERFACES) {
traits.set_public_property("interfaces", interfaces.into(), activation)?;
} else {
traits.set_public_property("interfaces", Value::Null, activation)?;
}
if flags.contains(DescribeTypeFlags::INCLUDE_VARIABLES) {
traits.set_public_property("variables", variables.into(), activation)?;
} else {
traits.set_public_property("variables", Value::Null, activation)?;
}
if flags.contains(DescribeTypeFlags::INCLUDE_ACCESSORS) {
traits.set_public_property("accessors", accessors.into(), activation)?;
} else {
traits.set_public_property("accessors", Value::Null, activation)?;
}
if flags.contains(DescribeTypeFlags::INCLUDE_METHODS) {
traits.set_public_property("methods", methods.into(), activation)?;
} else {
traits.set_public_property("methods", Value::Null, activation)?;
}
let mut bases_array = bases
.as_array_storage_mut(activation.context.gc_context)
@ -140,8 +166,6 @@ fn describe_internal_body<'gc>(
bases_array.push(super_name.into());
current_super_obj = super_obj.superclass_object();
}
} else {
traits.set_public_property("bases", Value::Null, activation)?;
}
// When we're describing a Class object, we use the class vtable (which hides instance properties)
@ -165,8 +189,6 @@ fn describe_internal_body<'gc>(
.to_qualified_name(activation.context.gc_context);
interfaces_array.push(interface_name.into());
}
} else {
traits.set_public_property("interfaces", Value::Null, activation)?;
}
// Implement the weird 'HIDE_NSURI_METHODS' behavior from avmplus:
@ -246,6 +268,9 @@ fn describe_internal_body<'gc>(
match prop {
Property::ConstSlot { slot_id } | Property::Slot { slot_id } => {
if !flags.contains(DescribeTypeFlags::INCLUDE_VARIABLES) {
continue;
}
let prop_class_name = vtable
.slot_class_name(*slot_id, activation.context.gc_context)?
.to_qualified_name_or_star(activation.context.gc_context);
@ -266,15 +291,28 @@ fn describe_internal_body<'gc>(
variable.set_public_property("name", prop_name.into(), activation)?;
variable.set_public_property("type", prop_class_name.into(), activation)?;
variable.set_public_property("access", access.into(), activation)?;
variable.set_public_property(
"uri",
uri.map_or(Value::Null, |u| u.into()),
activation,
)?;
if let Some(metadata) = trait_metadata {
variable.set_public_property("metadata", Value::Null, activation)?;
if flags.contains(DescribeTypeFlags::INCLUDE_METADATA) {
let metadata_object = ArrayObject::empty(activation)?;
write_metadata(metadata_object, &metadata, activation)?;
if let Some(metadata) = trait_metadata {
write_metadata(metadata_object, &metadata, activation)?;
}
variable.set_public_property("metadata", metadata_object.into(), activation)?;
}
variables_array.push(variable.into());
}
Property::Method { disp_id } => {
if !flags.contains(DescribeTypeFlags::INCLUDE_METHODS) {
continue;
}
let method = vtable
.get_full_method(*disp_id)
.unwrap_or_else(|| panic!("Missing method for id {disp_id:?}"));
@ -316,15 +354,22 @@ fn describe_internal_body<'gc>(
activation,
)?;
if let Some(uri) = uri {
method_obj.set_public_property("uri", uri.into(), activation)?;
}
method_obj.set_public_property(
"uri",
uri.map_or(Value::Null, |u| u.into()),
activation,
)?;
let params = write_params(&method.method, activation)?;
method_obj.set_public_property("parameters", params.into(), activation)?;
if let Some(metadata) = trait_metadata {
method_obj.set_public_property("metadata", Value::Null, activation)?;
if flags.contains(DescribeTypeFlags::INCLUDE_METADATA) {
let metadata_object = ArrayObject::empty(activation)?;
write_metadata(metadata_object, &metadata, activation)?;
if let Some(metadata) = trait_metadata {
write_metadata(metadata_object, &metadata, activation)?;
}
method_obj.set_public_property(
"metadata",
metadata_object.into(),
@ -334,6 +379,9 @@ fn describe_internal_body<'gc>(
methods_array.push(method_obj.into());
}
Property::Virtual { get, set } => {
if !flags.contains(DescribeTypeFlags::INCLUDE_ACCESSORS) {
continue;
}
let access = match (get, set) {
(Some(_), Some(_)) => "readwrite",
(Some(_), None) => "readonly",
@ -383,9 +431,11 @@ fn describe_internal_body<'gc>(
accessor_obj.set_public_property("access", access.into(), activation)?;
accessor_obj.set_public_property("type", accessor_type.into(), activation)?;
accessor_obj.set_public_property("declaredBy", declared_by.into(), activation)?;
if let Some(uri) = uri {
accessor_obj.set_public_property("uri", uri.into(), activation)?;
}
accessor_obj.set_public_property(
"uri",
uri.map_or(Value::Null, |u| u.into()),
activation,
)?;
let metadata_object = ArrayObject::empty(activation)?;
@ -401,12 +451,16 @@ fn describe_internal_body<'gc>(
}
}
if metadata_object.as_array_storage().unwrap().length() > 0 {
if flags.contains(DescribeTypeFlags::INCLUDE_METADATA)
&& metadata_object.as_array_storage().unwrap().length() > 0
{
accessor_obj.set_public_property(
"metadata",
metadata_object.into(),
activation,
)?;
} else {
accessor_obj.set_public_property("metadata", Value::Null, activation)?;
}
accessors_array.push(accessor_obj.into());
@ -427,6 +481,14 @@ fn describe_internal_body<'gc>(
traits.set_public_property("constructor", Value::Null, activation)?;
}
if flags.contains(DescribeTypeFlags::INCLUDE_METADATA) {
avm2_stub_method!(activation, "avmplus.describeTypeJSON", "top-level metadata");
let metadata_object = ArrayObject::empty(activation)?;
traits.set_public_property("metadata", metadata_object.into(), activation)?;
} else {
traits.set_public_property("metadata", Value::Null, activation)?;
}
Ok(traits)
}
@ -438,8 +500,7 @@ fn write_params<'gc>(
let mut params_array = params
.as_array_storage_mut(activation.context.gc_context)
.unwrap();
for (i, param) in method.signature().iter().enumerate() {
let index = i + 1;
for param in method.signature() {
let param_type_name = param
.param_type_name
.to_qualified_name_or_star(activation.context.gc_context);
@ -449,7 +510,6 @@ fn write_params<'gc>(
.classes()
.object
.construct(activation, &[])?;
param_obj.set_public_property("index", index.into(), activation)?;
param_obj.set_public_property("type", param_type_name.into(), activation)?;
param_obj.set_public_property("optional", optional.into(), activation)?;
params_array.push(param_obj.into());

View File

@ -0,0 +1,70 @@
package {
public class Test {
}
}
import com.ruffle.RuffleTest;
import flash.utils.getDefinitionByName;
import flash.utils.ByteArray;
import flash.system.System;
import avmplus.MyHelper;
var allFlags = [
0,
avmplus.INCLUDE_TRAITS | avmplus.HIDE_NSURI_METHODS,
avmplus.INCLUDE_TRAITS | avmplus.INCLUDE_BASES,
avmplus.INCLUDE_TRAITS | avmplus.INCLUDE_INTERFACES,
avmplus.INCLUDE_TRAITS | avmplus.INCLUDE_VARIABLES,
avmplus.INCLUDE_TRAITS | avmplus.INCLUDE_ACCESSORS,
avmplus.INCLUDE_TRAITS | avmplus.INCLUDE_METHODS,
avmplus.INCLUDE_TRAITS | avmplus.INCLUDE_METADATA,
avmplus.INCLUDE_TRAITS | avmplus.INCLUDE_CONSTRUCTOR,
avmplus.INCLUDE_TRAITS,
avmplus.INCLUDE_TRAITS | avmplus.USE_ITRAITS,
avmplus.INCLUDE_TRAITS | avmplus.HIDE_OBJECT
];
for each (var flags in allFlags) {
trace("Describing with flags: " + flags);
printObject(MyHelper.descType(new RuffleTest("first", false), flags));
trace();
}
function printObject(obj:Object, numTabs:int = 0):void {
var tabs:String = "";
for (var i:int = 0; i < numTabs; ++i) {
tabs += "\t";
}
var keys = [];
for (var k:* in obj) {
keys.push(k);
}
keys.sort();
for each (var key in keys) {
var v:* = obj[key];
row(tabs + key + " = " + v);
if (v) {
if (key == "methods" || key == "accessors" || key == "variables") {
v.sort(function(m1, m2) {
if (m1.name < m2.name) {
return -1;
}
else if (m1.name > m2.name) {
return 1;
}
else {
return 0;
}
});
}
printObject(v, numTabs + 1);
}
}
}
function row(...cols):void {
trace(cols.join(","));
}

View File

@ -0,0 +1,9 @@
package avmplus {
public class MyHelper {
public static function descType(param1:*, param2:uint) : Object
{
return describeTypeJSON(param1,param2);
}
}
}

View File

@ -0,0 +1,4 @@
package com.ruffle {
public interface MyInterface {
}
}

View File

@ -0,0 +1,27 @@
package com.ruffle {
import flash.utils.ByteArray;
public class RuffleBase {
public function RuffleBase() {
}
public var baseMyVar;
public const baseMyConst;
[BaseMyCustomMeta]
public function get baseGetterOnly():String {
return "";
};
public function set baseSetterOnly(val:Boolean) {
}
public function get baseGetterAndSetter():Boolean {
return true;
}
public function set baseGetterandSetter(val:Boolean) {
}
public function baseMyMethod() {
}
}
}

View File

@ -0,0 +1,27 @@
package com.ruffle {
import flash.utils.ByteArray;
public class RuffleTest extends RuffleBase implements MyInterface {
public function RuffleTest(first:String, second:Boolean) {
}
public var myVar;
public const myConst;
[MyCustomMeta]
public function get getterOnly():String {
return "";
};
public function set setterOnly(val:Boolean) {
}
public function get getterAndSetter():Boolean {
return true;
}
public function set getterandSetter(val:Boolean) {
}
public function myMethod(first:String, second:ByteArray) {
}
}
}

View File

@ -0,0 +1,288 @@
Describing with flags: 0
isDynamic = false
isFinal = false
isStatic = false
name = com.ruffle::RuffleTest
traits = null
Describing with flags: 257
isDynamic = false
isFinal = false
isStatic = false
name = com.ruffle::RuffleTest
traits = [object Object]
accessors = null
bases = null
constructor = null
interfaces = null
metadata = null
methods = null
variables = null
Describing with flags: 258
isDynamic = false
isFinal = false
isStatic = false
name = com.ruffle::RuffleTest
traits = [object Object]
accessors = null
bases = com.ruffle::RuffleBase,Object
0 = com.ruffle::RuffleBase
1 = Object
constructor = null
interfaces = null
metadata = null
methods = null
variables = null
Describing with flags: 260
isDynamic = false
isFinal = false
isStatic = false
name = com.ruffle::RuffleTest
traits = [object Object]
accessors = null
bases = null
constructor = null
interfaces = com.ruffle::MyInterface
0 = com.ruffle::MyInterface
metadata = null
methods = null
variables = null
Describing with flags: 264
isDynamic = false
isFinal = false
isStatic = false
name = com.ruffle::RuffleTest
traits = [object Object]
accessors = null
bases = null
constructor = null
interfaces = null
metadata = null
methods = null
variables = [object Object],[object Object],[object Object],[object Object]
0 = [object Object]
access = readonly
metadata = null
name = baseMyConst
type = *
uri = null
1 = [object Object]
access = readwrite
metadata = null
name = baseMyVar
type = *
uri = null
2 = [object Object]
access = readonly
metadata = null
name = myConst
type = *
uri = null
3 = [object Object]
access = readwrite
metadata = null
name = myVar
type = *
uri = null
Describing with flags: 272
isDynamic = false
isFinal = false
isStatic = false
name = com.ruffle::RuffleTest
traits = [object Object]
accessors = [object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object]
0 = [object Object]
access = readonly
declaredBy = com.ruffle::RuffleBase
metadata = null
name = baseGetterAndSetter
type = Boolean
uri = null
1 = [object Object]
access = readonly
declaredBy = com.ruffle::RuffleBase
metadata = null
name = baseGetterOnly
type = String
uri = null
2 = [object Object]
access = writeonly
declaredBy = com.ruffle::RuffleBase
metadata = null
name = baseGetterandSetter
type = Boolean
uri = null
3 = [object Object]
access = writeonly
declaredBy = com.ruffle::RuffleBase
metadata = null
name = baseSetterOnly
type = Boolean
uri = null
4 = [object Object]
access = readonly
declaredBy = com.ruffle::RuffleTest
metadata = null
name = getterAndSetter
type = Boolean
uri = null
5 = [object Object]
access = readonly
declaredBy = com.ruffle::RuffleTest
metadata = null
name = getterOnly
type = String
uri = null
6 = [object Object]
access = writeonly
declaredBy = com.ruffle::RuffleTest
metadata = null
name = getterandSetter
type = Boolean
uri = null
7 = [object Object]
access = writeonly
declaredBy = com.ruffle::RuffleTest
metadata = null
name = setterOnly
type = Boolean
uri = null
bases = null
constructor = null
interfaces = null
metadata = null
methods = null
variables = null
Describing with flags: 288
isDynamic = false
isFinal = false
isStatic = false
name = com.ruffle::RuffleTest
traits = [object Object]
accessors = null
bases = null
constructor = null
interfaces = null
metadata = null
methods = [object Object],[object Object],[object Object],[object Object],[object Object]
0 = [object Object]
declaredBy = com.ruffle::RuffleBase
metadata = null
name = baseMyMethod
parameters =
returnType = *
uri = null
1 = [object Object]
declaredBy = Object
metadata = null
name = hasOwnProperty
parameters = [object Object]
0 = [object Object]
optional = true
type = *
returnType = Boolean
uri = http://adobe.com/AS3/2006/builtin
2 = [object Object]
declaredBy = Object
metadata = null
name = isPrototypeOf
parameters = [object Object]
0 = [object Object]
optional = true
type = *
returnType = Boolean
uri = http://adobe.com/AS3/2006/builtin
3 = [object Object]
declaredBy = com.ruffle::RuffleTest
metadata = null
name = myMethod
parameters = [object Object],[object Object]
0 = [object Object]
optional = false
type = String
1 = [object Object]
optional = false
type = flash.utils::ByteArray
returnType = *
uri = null
4 = [object Object]
declaredBy = Object
metadata = null
name = propertyIsEnumerable
parameters = [object Object]
0 = [object Object]
optional = true
type = *
returnType = Boolean
uri = http://adobe.com/AS3/2006/builtin
variables = null
Describing with flags: 320
isDynamic = false
isFinal = false
isStatic = false
name = com.ruffle::RuffleTest
traits = [object Object]
accessors = null
bases = null
constructor = null
interfaces = null
metadata =
methods = null
variables = null
Describing with flags: 384
isDynamic = false
isFinal = false
isStatic = false
name = com.ruffle::RuffleTest
traits = [object Object]
accessors = null
bases = null
constructor = [object Object],[object Object]
0 = [object Object]
optional = false
type = String
1 = [object Object]
optional = false
type = Boolean
interfaces = null
metadata = null
methods = null
variables = null
Describing with flags: 256
isDynamic = false
isFinal = false
isStatic = false
name = com.ruffle::RuffleTest
traits = [object Object]
accessors = null
bases = null
constructor = null
interfaces = null
metadata = null
methods = null
variables = null
Describing with flags: 768
Describing with flags: 1280
isDynamic = false
isFinal = false
isStatic = false
name = com.ruffle::RuffleTest
traits = [object Object]
accessors = null
bases = null
constructor = null
interfaces = null
metadata = null
methods = null
variables = null

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1 @@
num_ticks = 1