Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions core/src/avm2/amf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -457,8 +457,12 @@ pub fn deserialize_value_impl<'gc>(
let class = alias_to_class(activation, name)?;

// Create an empty vector, as it has to exist in the map before reading children, in case they reference it
let empty_storage =
VectorStorage::new(0, *is_fixed, Some(class.inner_class_definition()));
let empty_storage = VectorStorage::new(
0,
*is_fixed,
Some(class.inner_class_definition()),
activation,
);
let obj = VectorObject::from_vector(empty_storage, activation);
object_map.insert(*id, obj.into());

Expand Down
5 changes: 5 additions & 0 deletions core/src/avm2/call_stack.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ impl<'gc> CallStack<'gc> {
self.stack.pop();
}

/// Iterate methods from most-recently-pushed to least-recently-pushed.
pub fn iter_top_down(&self) -> impl Iterator<Item = Method<'gc>> + '_ {
self.stack.iter().rev().copied()
}

pub fn display(&self, output: &mut WString) {
for method in self.stack.iter().rev() {
output.push_utf8("\n\tat ");
Expand Down
2 changes: 1 addition & 1 deletion core/src/avm2/globals/flash/display/graphics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1347,7 +1347,7 @@
) -> Result<Value<'gc>, Error<'gc>> {
avm2_stub_method!(activation, "flash.display.Graphics", "readGraphicsData");
let value_type = activation.avm2().class_defs().igraphicsdata;
let new_storage = VectorStorage::new(0, false, Some(value_type));
let new_storage = VectorStorage::new(0, false, Some(value_type), activation);

Check warning on line 1350 in core/src/avm2/globals/flash/display/graphics.rs

View workflow job for this annotation

GitHub Actions / Coverage Report

Coverage

Uncovered line (1350)
Ok(VectorObject::from_vector(new_storage, activation).into())
}

Expand Down
2 changes: 1 addition & 1 deletion core/src/avm2/globals/flash/geom/transform.rs
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ pub fn matrix3d_to_object<'gc>(
activation: &mut Activation<'_, 'gc>,
) -> Result<Value<'gc>, Error<'gc>> {
let number = activation.avm2().class_defs().number;
let mut raw_data_storage = VectorStorage::new(16, true, Some(number));
let mut raw_data_storage = VectorStorage::new(16, true, Some(number), activation);
for (i, data) in matrix.raw_data.iter().enumerate() {
raw_data_storage.set(i, Value::Number(*data), activation)?;
}
Expand Down
8 changes: 4 additions & 4 deletions core/src/avm2/globals/vector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ pub fn call_handler<'gc>(
.get_public_property(istr!("length"), activation)?
.coerce_to_i32(activation)?;

let mut new_storage = VectorStorage::new(0, false, value_type);
let mut new_storage = VectorStorage::new(0, false, value_type, activation);
new_storage.reserve_exact(length as usize);

let value_type_for_coercion = new_storage.value_type_for_coercion(activation);
Expand Down Expand Up @@ -269,7 +269,7 @@ pub fn filter<'gc>(
.param()
.expect("Receiver is parametrized vector"); // technically unreachable

let mut new_storage = VectorStorage::new(0, false, value_type);
let mut new_storage = VectorStorage::new(0, false, value_type, activation);

let callback = match args.try_get_function(0) {
None => return Ok(VectorObject::from_vector(new_storage, activation).into()),
Expand Down Expand Up @@ -366,7 +366,7 @@ pub fn map<'gc>(
.param()
.expect("Receiver is parametrized vector"); // technically unreachable

let mut new_storage = VectorStorage::new(0, false, value_type);
let mut new_storage = VectorStorage::new(0, false, value_type, activation);

let callback = match args.try_get_function(0) {
None => {
Expand Down Expand Up @@ -571,7 +571,7 @@ pub fn slice<'gc>(
let from = vs.clamp_parameter_index(from);
let to = vs.clamp_parameter_index(to);

let mut new_vs = VectorStorage::new(0, false, value_type);
let mut new_vs = VectorStorage::new(0, false, value_type, activation);

if to > from {
for value in vs.iter().skip(from).take(to - from) {
Expand Down
6 changes: 3 additions & 3 deletions core/src/avm2/object/vector_object.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
activation.gc(),
VectorObjectData {
base,
vector: RefLock::new(VectorStorage::new(0, false, param_type)),
vector: RefLock::new(VectorStorage::new(0, false, param_type, activation)),
},
))
.into())
Expand Down Expand Up @@ -152,8 +152,8 @@

let type_of = self.0.vector.borrow().value_type_for_coercion(activation);
let value = match value.coerce_to_type(activation, type_of)? {
Value::Undefined => self.0.vector.borrow().default(),
Value::Null => self.0.vector.borrow().default(),
Value::Undefined => self.0.vector.borrow().default(activation),

Check warning on line 155 in core/src/avm2/object/vector_object.rs

View workflow job for this annotation

GitHub Actions / Coverage Report

Coverage

Uncovered line (155)
Value::Null => self.0.vector.borrow().default(activation),
v => v,
};

Expand Down
50 changes: 45 additions & 5 deletions core/src/avm2/vector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
use crate::avm2::activation::Activation;
use crate::avm2::class::Class;
use crate::avm2::error::{make_error_1125, make_error_1126};
use crate::avm2::method::MethodKind;
use crate::avm2::value::Value;
use gc_arena::Collect;
use std::cmp::{max, min};
Expand Down Expand Up @@ -40,7 +41,12 @@
}

impl<'gc> VectorStorage<'gc> {
pub fn new(length: usize, is_fixed: bool, value_type: Option<Class<'gc>>) -> Self {
pub fn new(
length: usize,
is_fixed: bool,
value_type: Option<Class<'gc>>,
activation: &mut Activation<'_, 'gc>,
) -> Self {
let storage = Vec::new();

let mut self_vec = VectorStorage {
Expand All @@ -49,7 +55,9 @@
value_type,
};

self_vec.storage.resize(length, self_vec.default());
self_vec
.storage
.resize(length, self_vec.default(activation));

self_vec
}
Expand Down Expand Up @@ -99,16 +107,18 @@
activation: &mut Activation<'_, 'gc>,
) -> Result<(), Error<'gc>> {
self.check_fixed(activation)?;
self.storage.resize(new_length, self.default());
self.storage.resize(new_length, self.default(activation));

Ok(())
}

/// Get the default value for this vector.
pub fn default(&self) -> Value<'gc> {
pub fn default(&self, activation: &mut Activation<'_, 'gc>) -> Value<'gc> {
if let Some(value_type) = self.value_type {
if value_type.is_builtin_numeric() {
Value::Integer(0)
} else if value_type.is_builtin_boolean() {
bool_default(activation)
} else {
Value::Null
}
Expand Down Expand Up @@ -180,7 +190,7 @@
activation: &mut Activation<'_, 'gc>,
) -> Result<(), Error<'gc>> {
if !self.is_fixed && pos == self.length() {
self.storage.resize(pos + 1, self.default());
self.storage.resize(pos + 1, self.default(activation));
}

if let Some(v) = self.storage.get_mut(pos) {
Expand Down Expand Up @@ -373,3 +383,33 @@
&self.storage
}
}

/// Determine the default value for `Vector.<Boolean>` holes.
///
/// Flash returns `null` when the fill happens from a script initializer
/// (top-level code), regardless of SWF version. From any other context the
/// default is `false` for SWF version >= 14 and `null` for earlier versions.
fn bool_default<'gc>(activation: &mut Activation<'_, 'gc>) -> Value<'gc> {
let call_stack = activation.avm2().call_stack();
let call_stack = call_stack.borrow();

let calling_method = call_stack
.iter_top_down()
.find(|m| matches!(m.method_kind(), MethodKind::Bytecode { .. }));

if let Some(method) = calling_method {
let is_script_init = method
.bound_class()
.is_some_and(|c| c.is_script_traits() && c.instance_init() == Some(method));

if is_script_init {
return Value::Null;
}
}

Check warning on line 408 in core/src/avm2/vector.rs

View workflow job for this annotation

GitHub Actions / Coverage Report

Coverage

Uncovered line (408)

if activation.caller_movie_or_root().version() >= 14 {
Value::Bool(false)
} else {
Value::Null
}
}
31 changes: 31 additions & 0 deletions tests/tests/swfs/avm2/vector_boolean_default_constructor/Test.as
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Reproduction from Ruffle issue #23317.
// https://github.com/ruffle-rs/ruffle/issues/23317
//
// Compile with Apache Flex SDK's mxmlc. The reporter used:
// mxmlc -o test.swf -debug Test.as
// which defaults to a modern SWF version (>=14), where Flash and Ruffle
// disagree on the constructor case. To reproduce the SWF-version split,
// compile with:
// mxmlc -o test_v13.swf -debug Test.as -swf-version 13 // Flash agrees with Ruffle
// mxmlc -o test_v14.swf -debug Test.as -swf-version 14 // Flash diverges
//
// Flash output (SWF version >= 14):
// null <- top-level allocation
// false <- constructor allocation
//
// Ruffle output before the fix:
// null
// null

package {
import flash.display.Sprite;
public class Test extends Sprite {
public function Test() {
var vec:Vector.<Boolean> = new Vector.<Boolean>(1, true);
trace(vec[0]); // false on Flash, null on Ruffle (before fix)
}
}
}

var vec:Vector.<Boolean> = new Vector.<Boolean>(1, true);
trace(vec[0]); // null on both
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
null
false
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
num_frames = 1
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Reproduction from Ruffle issue #23317.
// https://github.com/ruffle-rs/ruffle/issues/23317
//
// Compile with Apache Flex SDK's mxmlc. The reporter used:
// mxmlc -o test.swf -debug Test.as
// which defaults to a modern SWF version (>=14), where Flash and Ruffle
// disagree on the constructor case. To reproduce the SWF-version split,
// compile with:
// mxmlc -o test_v13.swf -debug Test.as -swf-version 13 // Flash agrees with Ruffle
// mxmlc -o test_v14.swf -debug Test.as -swf-version 14 // Flash diverges
//
// Flash output (SWF version >= 14):
// null <- top-level allocation
// false <- constructor allocation
//
// Ruffle output before the fix:
// null
// null

package {
import flash.display.Sprite;
public class Test extends Sprite {
public function Test() {
var vec:Vector.<Boolean> = new Vector.<Boolean>(1, true);
trace(vec[0]); // false on Flash, null on Ruffle (before fix)
}
}
}

var vec:Vector.<Boolean> = new Vector.<Boolean>(1, true);
trace(vec[0]); // null on both
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
null
null
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
num_frames = 1
Loading