avm2: Implemented newcatch and basic exception handling
This commit is contained in:
parent
54f9824ce0
commit
26b41199fb
|
@ -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));
|
||||
|
|
|
@ -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>,
|
||||
|
|
|
@ -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()))
|
||||
}
|
||||
|
|
|
@ -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()?;
|
||||
|
|
|
@ -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>,
|
||||
}
|
||||
|
||||
|
|
|
@ -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())?)
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
// Trying to raise and catch an error
|
||||
RangeError
|
Binary file not shown.
Loading…
Reference in New Issue