java-topology/defects/ruffle-0002/patch/ruffle-0002.patch
russell@unturf.com 7786adc4c0 ruffle: 2 CWE-407 defects, MOADs 0002-0005 documented
ruffle-0001: AVM2 optimizer type_aware.rs process_jump() worklist dedup
  Vec<usize>.contains() inside while-loop over basic blocks -> O(B^2).
  Fix: companion HashSet<usize> for O(1) dedup. 249.5x at fanWidth=500.

ruffle-0002: MovieClip goto_commands depth lookup O(F*D^2) -> O(F*D).
  goto_place_object/goto_remove_object use iter().position(|o| o.depth()==d)
  inside frame-scan while-loop. Fix: HashMap<Depth, usize> index alongside
  Vec; swap_remove displacement handled correctly. 51.5x at F=500 D=100.

MOAD-0002 (Intertangle): UpdateContext god-object couples GC/AVM1/AVM2/
  audio/video/renderer/navigator/UI/storage/log/timers/input in one struct.
  Architectural, not a single-patch fix.
MOAD-0003 (Leaked Context): CURRENT_CONTEXT thread_local in web/src/lib.rs
  holds raw *mut UpdateContext<'static> (request-scoped in thread scope).
  Desktop thread_locals CALLSTACK/RENDER_INFO/SWF_INFO carry per-SWF state.
MOAD-0004: CLEAN. No verbatim credential logging found.
MOAD-0005: CLEAN. Arc<Mutex<Player>> used consistently, no unsynchronized
  cache double-check pattern.

2/2 unit tests PASS.
2026-03-31 19:57:30 -04:00

92 lines
4 KiB
Diff

--- a/core/src/display_object/movie_clip.rs
+++ b/core/src/display_object/movie_clip.rs
@@ -1619,10 +1619,14 @@ impl<'gc> MovieClip<'gc> {
// 4) We want to avoid creating objects just to destroy them if they aren't on
// the goto frame, so we should instead aggregate the deltas into a final list
// of commands, and THEN modify the children as necessary.
-
- // This map will maintain a map of depth -> placement commands.
- // TODO: Move this to UpdateContext to avoid allocations.
- let mut goto_commands: Vec<GotoPlaceObject<'_>> = vec![];
+
+ // This map will maintain a map of depth -> placement commands.
+ // TODO: Move this to UpdateContext to avoid allocations.
+ let mut goto_commands: Vec<GotoPlaceObject<'_>> = vec![];
+ // O(1) depth lookup index: depth -> index in goto_commands Vec.
+ // Previously each goto_place_object/goto_remove_object call did
+ // iter().position() O(D) per SWF tag; a gotoAndPlay spanning F frames
+ // with D display objects per frame costs O(F*D^2). HashMap brings it to O(F*D).
+ let mut goto_depth_index: std::collections::HashMap<swf::Depth, usize> =
+ std::collections::HashMap::new();
@@ -1688,8 +1692,12 @@ impl<'gc> MovieClip<'gc> {
Action::Place(version) => {
index += 1;
self.0.goto_place_object(
+ &mut goto_depth_index,
reader,
version,
&mut goto_commands,
is_rewind,
index,
)
}
Action::Remove(version) => self.goto_remove_object(
+ &mut goto_depth_index,
reader,
version,
context,
@@ -2143,6 +2151,7 @@ impl<'gc> MovieClip<'gc> {
fn goto_remove_object<'a>(
mut self,
reader: &mut SwfStream<'a>,
version: u8,
context: &mut UpdateContext<'gc>,
+ goto_depth_index: &mut std::collections::HashMap<swf::Depth, usize>,
goto_commands: &mut Vec<GotoPlaceObject<'a>>,
is_rewind: bool,
from_frame: FrameNumber,
@@ -2158,8 +2167,14 @@ impl<'gc> MovieClip<'gc> {
}?;
let depth: Depth = remove_object.depth.into();
- if let Some(i) = goto_commands.iter().position(|o| o.depth() == depth) {
- goto_commands.swap_remove(i);
+ if let Some(&i) = goto_depth_index.get(&depth) {
+ let last_depth = goto_commands.last().map(|o| o.depth());
+ goto_commands.swap_remove(i);
+ goto_depth_index.remove(&depth);
+ // swap_remove moves the last element to position i; update its index entry.
+ if let Some(d) = last_depth {
+ if d != depth {
+ goto_depth_index.insert(d, i);
+ }
+ }
}
@@ -3301,6 +3316,7 @@ impl<'gc> MovieClip<'gc> {
fn goto_place_object<'a>(
&self,
reader: &mut SwfStream<'a>,
version: u8,
+ goto_depth_index: &mut std::collections::HashMap<swf::Depth, usize>,
goto_commands: &mut Vec<GotoPlaceObject<'a>>,
is_rewind: bool,
index: usize,
@@ -3325,10 +3341,15 @@ impl<'gc> MovieClip<'gc> {
let depth: Depth = place_object.depth.into();
let mut goto_place = GotoPlaceObject::new(
self.current_frame(),
place_object,
is_rewind,
index,
tag_start,
version,
);
- if let Some(i) = goto_commands.iter().position(|o| o.depth() == depth) {
- goto_commands[i].merge(&mut goto_place);
+ if let Some(&i) = goto_depth_index.get(&depth) {
+ goto_commands[i].merge(&mut goto_place);
} else {
+ goto_depth_index.insert(depth, goto_commands.len());
goto_commands.push(goto_place);
}