avm2: Add new 'stub-report' binary crate

This writes the AVM2 stub report to the specified path.
This commit is contained in:
Aaron Hill 2024-01-21 14:24:45 -05:00
parent 25b5c7b4e2
commit 78873e3670
10 changed files with 86 additions and 23 deletions

9
Cargo.lock generated
View File

@ -4989,6 +4989,15 @@ version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "stub-report"
version = "0.1.0"
dependencies = [
"anyhow",
"clap",
"ruffle_core",
]
[[package]]
name = "swf"
version = "0.2.0"

View File

@ -20,6 +20,8 @@ members = [
"ruffle_gc_arena",
"stub-report",
"video",
"video/software",
@ -47,6 +49,8 @@ naga = { version = "0.19.0", features = ["wgsl-out"] }
naga_oil = "0.12.0"
wgpu = "0.19.1"
egui = "0.25.0"
clap = { version = "4.4.18", features = ["derive"] }
anyhow = "1.0"
[workspace.lints.rust]
# Clippy nightly often adds new/buggy lints that we want to ignore.

View File

@ -58,6 +58,7 @@ mod qname;
mod regexp;
mod scope;
mod script;
#[cfg(feature = "known_stubs")]
pub mod specification;
mod string;
mod stubs;

View File

@ -12,7 +12,6 @@ use crate::avm2::Multiname;
use crate::avm2::QName;
use gc_arena::{Collect, GcCell, GcWeakCell, Mutation};
use ruffle_wstr::WStr;
use std::cell::Ref;
use super::class::Class;
use super::error::error;
@ -349,10 +348,6 @@ impl<'gc> Domain<'gc> {
Ref::map(self.0.read(), |this| &this.defs)
}
pub fn classes(&self) -> Ref<PropertyMap<'gc, GcCell<'gc, Class<'gc>>>> {
Ref::map(self.0.read(), |this| &this.classes)
}
pub fn is_default_domain_memory(&self) -> bool {
let read = self.0.read();
read.domain_memory.expect("Missing domain memory").as_ptr()

View File

@ -148,7 +148,7 @@ impl<'gc> ScriptObjectData<'gc> {
/// Retrieve the values stored directly on this ScriptObjectData.
///
/// This should only be used for debugging purposes.
pub fn values(&self) -> &DynamicMap<StringOrObject<'gc>, Value<'gc>> {
pub fn values(&self) -> &DynamicMap<DynamicKey<'gc>, Value<'gc>> {
&self.values
}

View File

@ -1,4 +1,4 @@
use crate::avm2::dynamic_map::{DynamicMap, StringOrObject};
use crate::avm2::dynamic_map::DynamicKey;
use crate::avm2::function::Executable;
use crate::avm2::method::{Method, ParamConfig};
use crate::avm2::object::TObject;
@ -11,6 +11,7 @@ use fnv::{FnvHashMap, FnvHashSet};
use serde::Serialize;
use std::borrow::Cow;
use std::fs::File;
use std::path::Path;
use std::process::exit;
fn is_false(b: &bool) -> bool {
@ -298,12 +299,14 @@ impl Definition {
let prototype = class_object.prototype();
let prototype_base = prototype.base();
let prototype_values: &DynamicMap<StringOrObject<'gc>, Value<'gc>> =
prototype_base.values();
let prototype_values = prototype_base.values();
for (key, value) in prototype_values.as_hashmap().iter() {
let name = match key {
StringOrObject::String(name) => *name,
StringOrObject::Object(object) => {
DynamicKey::String(name) => *name,
DynamicKey::Uint(key) => {
AvmString::new_utf8(activation.context.gc_context, key.to_string())
}
DynamicKey::Object(object) => {
Value::Object(*object).coerce_to_string(activation).unwrap()
}
};
@ -321,13 +324,13 @@ impl Definition {
activation.avm2(),
class.read().class_traits(),
&mut definition.static_traits,
&stubs,
stubs,
);
Self::fill_traits(
activation.avm2(),
class.read().instance_traits(),
&mut definition.instance_traits,
&stubs,
stubs,
);
definition
@ -454,10 +457,12 @@ impl Definition {
}
}
pub fn capture_specification(context: &mut UpdateContext) {
let mut definitions = FnvHashMap::<String, Definition>::default();
#[allow(unreachable_code, unused_variables, clippy::diverging_sub_expression)]
pub fn capture_specification(context: &mut UpdateContext, output: &Path) {
let stubs = crate::stub::get_known_stubs();
let mut definitions = FnvHashMap::<String, Definition>::default();
let defs = context.avm2.playerglobals_domain.defs().clone();
let mut activation = Activation::from_nothing(context.reborrow());
for (name, namespace, _) in defs.iter() {
@ -510,7 +515,7 @@ pub fn capture_specification(context: &mut UpdateContext) {
);
}
}
serde_json::to_writer_pretty(&File::create("implementation.json").unwrap(), &definitions)
.unwrap();
serde_json::to_writer_pretty(&File::create(output).unwrap(), &definitions).unwrap();
tracing::info!("Wrote stub report to {output:?}");
exit(0);
}

View File

@ -5,10 +5,7 @@ use crate::avm1::Object;
use crate::avm1::SystemProperties;
use crate::avm1::VariableDumper;
use crate::avm1::{Activation, ActivationIdentifier};
use crate::avm1::{ScriptObject, TObject, Value};
use crate::avm1::{TObject, Value};
use crate::avm2::api_version::ApiVersion;
use crate::avm2::specification::capture_specification;
use crate::avm2::{
object::TObject as _, Activation as Avm2Activation, Avm2, CallStack, Object as Avm2Object,
};
@ -2124,6 +2121,8 @@ pub struct PlayerBuilder {
frame_rate: Option<f64>,
external_interface_providers: Vec<Box<dyn ExternalInterfaceProvider>>,
fs_command_provider: Box<dyn FsCommandProvider>,
#[cfg(feature = "known_stubs")]
stub_report_output: Option<std::path::PathBuf>,
}
impl PlayerBuilder {
@ -2172,6 +2171,8 @@ impl PlayerBuilder {
frame_rate: None,
external_interface_providers: vec![],
fs_command_provider: Box::new(NullFsCommandProvider),
#[cfg(feature = "known_stubs")]
stub_report_output: None,
}
}
@ -2361,6 +2362,14 @@ impl PlayerBuilder {
self
}
#[cfg(feature = "known_stubs")]
/// Sets the output path for the stub report. When set, the player
/// will write the report to this path and exit the process.
pub fn with_stub_report_output(mut self, output: std::path::PathBuf) -> Self {
self.stub_report_output = Some(output);
self
}
fn create_gc_root<'gc>(
gc_context: &'gc gc_arena::Mutation<'gc>,
player_version: u8,
@ -2551,7 +2560,10 @@ impl PlayerBuilder {
stage.set_allow_fullscreen(context, self.allow_fullscreen);
stage.post_instantiation(context, None, Instantiator::Movie, false);
stage.build_matrices(context);
capture_specification(context);
#[cfg(feature = "known_stubs")]
if let Some(stub_path) = self.stub_report_output {
crate::avm2::specification::capture_specification(context, &stub_path);
}
});
player_lock.gc_arena.borrow().mutate(|context, root| {
let call_stack = root.data.read().avm2.call_stack();

View File

@ -11,7 +11,7 @@ version.workspace = true
workspace = true
[dependencies]
clap = { version = "4.4.18", features = ["derive"] }
clap = { workspace = true }
futures = "0.3"
ruffle_core = { path = "../core", features = ["deterministic", "default_font"] }
ruffle_render_wgpu = { path = "../render/wgpu", features = ["clap"] }
@ -19,8 +19,8 @@ image = { version = "0.24.8", default-features = false, features = ["png"] }
log = "0.4"
walkdir = "2.4.0"
indicatif = "0.17"
anyhow = "1.0"
rayon = "1.8.1"
anyhow = { workspace = true }
[features]
avm_debug = ["ruffle_core/avm_debug"]

16
stub-report/Cargo.toml Normal file
View File

@ -0,0 +1,16 @@
[package]
name = "stub-report"
authors.workspace = true
edition.workspace = true
homepage.workspace = true
license.workspace = true
repository.workspace = true
version.workspace = true
[dependencies]
clap = { workspace = true }
anyhow = { workspace = true }
ruffle_core = { path = "../core", features = ["known_stubs"] }
[lints]
workspace = true

21
stub-report/src/main.rs Normal file
View File

@ -0,0 +1,21 @@
use std::path::PathBuf;
use anyhow::Result;
use clap::Parser;
use ruffle_core::PlayerBuilder;
#[derive(Parser, Debug)]
#[clap(name = "Ruffle Stub Report Generator", author, version)]
struct Opt {
/// The file to store the stub report output
#[clap(name = "output")]
output_path: PathBuf,
}
fn main() -> Result<()> {
let opt: Opt = Opt::parse();
PlayerBuilder::new()
.with_stub_report_output(opt.output_path)
.build();
Ok(())
}