avm2: Implemented newcatch and basic exception handling

This commit is contained in:
dowgird 2022-07-30 19:12:41 +02:00 committed by Adrian Wielgosik
parent 54f9824ce0
commit 26b41199fb
10 changed files with 124 additions and 6 deletions

View File

@ -22,7 +22,7 @@ use std::borrow::Cow;
use std::cmp::{min, Ordering};
use swf::avm2::read::Reader;
use swf::avm2::types::{
Class as AbcClass, Index, Method as AbcMethod, MethodFlags as AbcMethodFlags,
Class as AbcClass, Exception, Index, Method as AbcMethod, MethodFlags as AbcMethodFlags,
Multiname as AbcMultiname, Namespace as AbcNamespace, Op,
};
@ -861,6 +861,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
Op::SetSuper { index } => self.op_set_super(method, index),
Op::In => self.op_in(),
Op::PushScope => self.op_push_scope(),
Op::NewCatch { index } => self.op_newcatch(method, index),
Op::PushWith => self.op_push_with(),
Op::PopScope => self.op_pop_scope(),
Op::GetOuterScope { index } => self.op_get_outer_scope(index),
@ -999,9 +1000,27 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
_ => self.unknown_op(op),
};
if let Err(e) = result {
log::error!("AVM2 error: {}", e);
return Err(e);
if let Err(error) = result {
if let Some(body) = method.body() {
for e in body.exceptions.iter() {
if instruction_start >= e.from_offset as usize
&& instruction_start < e.to_offset as usize
&& e.type_name.0 == 0
// Currently we support only typeless catch clauses
{
// Emulate pushing the exception object
let ws = WString::from_utf8_owned(error.to_string());
let exception = AvmString::new(self.context.gc_context, ws);
self.context.avm2.push(exception);
reader.seek_absolute(full_data, e.target_offset as usize);
return Ok(FrameControl::Continue);
}
}
}
log::error!("AVM2 error: {}", error);
return Err(error);
}
result
} else if let Err(e) = op {
@ -1550,6 +1569,26 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
Ok(FrameControl::Continue)
}
fn op_newcatch(
&mut self,
method: Gc<'gc, BytecodeMethod<'gc>>,
index: Index<Exception>,
) -> Result<FrameControl<'gc>, Error> {
if let Some(body) = method.body() {
let ex = &body.exceptions[index.0 as usize];
let vname = ex.variable_name;
let qname = QName::from_abc_multiname(
method.translation_unit(),
vname,
self.context.gc_context,
)?;
let so = ScriptObject::catch_scope(self.context.gc_context, &qname);
self.context.avm2.push(so);
}
Ok(FrameControl::Continue)
}
fn op_push_scope(&mut self) -> Result<FrameControl<'gc>, Error> {
let object = self.context.avm2.pop().coerce_to_object(self)?;
self.scope_stack.push(Scope::new(object));

View File

@ -5,7 +5,7 @@ use crate::avm2::names::Multiname;
use crate::avm2::object::{ClassObject, FunctionObject, Object, ObjectPtr, TObject};
use crate::avm2::value::Value;
use crate::avm2::vtable::VTable;
use crate::avm2::Error;
use crate::avm2::{Error, QName};
use crate::string::AvmString;
use fnv::FnvHashMap;
use gc_arena::{Collect, GcCell, MutationContext};
@ -102,6 +102,17 @@ impl<'gc> ScriptObject<'gc> {
))
.into()
}
/// A special case for `newcatch` implementation. Basically a variable (q)name
/// which maps to slot 1.
pub fn catch_scope(mc: MutationContext<'gc, '_>, qname: &QName<'gc>) -> Object<'gc> {
let mut base = ScriptObjectData::custom_new(None, None);
let vt = VTable::newcatch(mc, &qname);
base.set_vtable(vt);
base.install_slot();
base.install_slot();
ScriptObject(GcCell::allocate(mc, base)).into()
}
}
impl<'gc> ScriptObjectData<'gc> {
@ -128,6 +139,10 @@ impl<'gc> ScriptObjectData<'gc> {
}
}
pub fn install_slot(&mut self) {
self.slots.push(Value::Undefined);
}
pub fn get_property_local(
&self,
multiname: &Multiname<'gc>,

View File

@ -65,6 +65,33 @@ impl<'gc> VTable<'gc> {
))
}
/// A special case for newcatch. A single variable (q)name that maps to slot 1.
pub fn newcatch(mc: MutationContext<'gc, '_>, vname: &QName<'gc>) -> Self {
let mut rt = PropertyMap::new();
rt.insert(
*vname,
Property::Slot {
slot_id: 1,
class: PropertyClass::Any,
},
);
let vt = VTable(GcCell::allocate(
mc,
VTableData {
defining_class: None,
scope: None,
protected_namespace: None,
resolved_traits: rt,
method_table: vec![],
default_slots: vec![None, None],
},
));
return vt;
}
pub fn duplicate(self, mc: MutationContext<'gc, '_>) -> Self {
VTable(GcCell::allocate(mc, self.0.read().clone()))
}

View File

@ -29,6 +29,11 @@ impl<'a> Reader<'a> {
ReadSwfExt::seek(self, data, relative_offset as isize)
}
#[inline]
pub fn seek_absolute(&mut self, data: &'a [u8], pos: usize) {
ReadSwfExt::seek_absolute(self, data, pos)
}
pub fn read(&mut self) -> Result<AbcFile> {
let minor_version = self.read_u16()?;
let major_version = self.read_u16()?;

View File

@ -139,7 +139,7 @@ pub struct Exception {
pub from_offset: u32,
pub to_offset: u32,
pub target_offset: u32,
pub variable_name: Index<String>,
pub variable_name: Index<Multiname>,
pub type_name: Index<Multiname>,
}

View File

@ -20,6 +20,11 @@ pub trait ReadSwfExt<'a> {
*self.as_mut_slice() = &data[pos..];
}
fn seek_absolute(&mut self, data: &'a [u8], pos: usize) {
let pos = pos.min(data.len());
*self.as_mut_slice() = &data[pos..];
}
#[inline]
fn read_u8(&mut self) -> Result<u8> {
Ok(ReadBytesExt::read_u8(self.as_mut_slice())?)

View File

@ -449,6 +449,7 @@ swf_tests! {
(as3_string_length, "avm2/string_length", 1),
(as3_string_match, "avm2/string_match", 1),
(as3_string_replace, "avm2/string_replace", 1),
(as3_try_catch, "avm2/try_catch", 1),
(as3_string_slice_substr_substring, "avm2/string_slice_substr_substring", 1),
(as3_string_split, "avm2/string_split", 1),
(as3_subtract, "avm2/subtract", 1),

View File

@ -0,0 +1,24 @@
// compiled with mxmlc
package {
import flash.display.MovieClip;
public class Test extends MovieClip {
public function Test() {
trace("// Trying to raise and catch an error")
try {
var v = new Vector.<String>();
v.fixed = true;
v.push("a");
v.push("bcd");
} catch(foobar) {
// This cuts the error description to "RangeError"
// which is a cheat to mask the fact that the full error
// descriptions are different between Ruffle and Flash
trace(foobar.toString().slice(0,10));
}
}
}
}

View File

@ -0,0 +1,2 @@
// Trying to raise and catch an error
RangeError

Binary file not shown.