1mod patchable_buf_writer;
2mod primitives;
3
4use crate::patchable_buf_writer::{BufferHole, PatchableBufWriter};
5use crate::primitives::{emit_c_string, emit_le32_f64, emit_le_f32, emit_le_i16, emit_le_i32, emit_le_u16, emit_u8};
6use avm1_types::cfg::CfgLabel;
7use avm1_types::raw;
8use avm1_types::raw::FromCfgActionError;
9use avm1_types::{cfg, ActionHeader, CatchTarget, GetUrl2Method, PushValue};
10use std::collections::HashMap;
11use std::convert::{TryFrom, TryInto};
12use std::io;
13use std::io::Write;
14
15pub fn emit_cfg(value: &cfg::Cfg) -> io::Result<Vec<u8>> {
16 let mut avm1_writer = PatchableBufWriter::new();
17 write_cfg(&mut avm1_writer, value)?;
18 Ok(avm1_writer.complete())
19}
20
21fn write_cfg(writer: &mut PatchableBufWriter, value: &cfg::Cfg) -> io::Result<()> {
22 write_hard_cfg(writer, value, true)
23}
24
25fn offset_delta_i16(source: usize, target: usize) -> Option<i16> {
27 if target >= source {
28 i16::try_from(target - source).ok()
30 } else {
31 let x: usize = source - target;
34 let x: usize = x - 1;
36 let x = i16::try_from(x).ok()?;
39 let x = x.wrapping_neg();
42 let x = x - 1;
44 Some(x)
45 }
46}
47
48fn write_hard_cfg(writer: &mut PatchableBufWriter, value: &cfg::Cfg, append_end_action: bool) -> io::Result<()> {
49 let wi: WriteInfo = write_soft_cfg(writer, value, None)?;
50 let end_offset = writer.len();
51 if append_end_action {
52 write_raw_action(writer, &raw::Action::End)?;
53 }
54
55 for (offset, (hole, target_label)) in wi.jumps.into_iter() {
56 let target_offset: usize = match target_label.as_ref() {
57 Some(cfg_label) => wi.blocks.get(cfg_label).cloned().expect("TargetLabelNotFound"),
58 None => end_offset,
59 };
60 let offset = offset + 2; let delta = offset_delta_i16(offset, target_offset).expect("TargetOffsetOutOfReach");
62 hole.patch(writer, delta);
63 }
64
65 Ok(())
66}
67
68struct WriteInfo {
69 jumps: HashMap<usize, (BufferHole<i16>, Option<cfg::CfgLabel>)>,
70 blocks: HashMap<cfg::CfgLabel, usize>,
71}
72
73impl WriteInfo {
74 pub fn new() -> Self {
75 Self {
76 jumps: HashMap::new(),
77 blocks: HashMap::new(),
78 }
79 }
80
81 pub fn extend(&mut self, wi: Self) {
82 self.jumps.extend(wi.jumps.into_iter());
83 self.blocks.extend(wi.blocks.into_iter());
84 }
85}
86
87fn write_soft_cfg(
88 writer: &mut PatchableBufWriter,
89 value: &cfg::Cfg,
90 fallthrough_next: Option<&cfg::CfgLabel>,
91) -> io::Result<WriteInfo> {
92 let mut res = WriteInfo::new();
93
94 for (i, block) in value.blocks.iter().enumerate() {
95 let cur_next: Option<&cfg::CfgLabel> = match value.blocks.get(i + 1) {
96 Some(x) => Some(&x.label),
97 None => fallthrough_next,
98 };
99 let wi: WriteInfo = write_block(writer, block, cur_next)?;
100 res.extend(wi);
101 }
102
103 Ok(res)
104}
105
106fn write_block(
107 writer: &mut PatchableBufWriter,
108 value: &cfg::CfgBlock,
109 fallthrough_next: Option<&cfg::CfgLabel>,
110) -> io::Result<WriteInfo> {
111 let mut res = WriteInfo::new();
112
113 res.blocks.insert(value.label.clone(), writer.len());
114
115 for action in value.actions.iter().cloned() {
116 match raw::Action::try_from(action) {
117 Ok(raw) => write_raw_action(writer, &raw)?,
118 Err(FromCfgActionError::DefineFunction(action)) => write_define_function(writer, &action)?,
119 Err(FromCfgActionError::DefineFunction2(action)) => write_define_function2(writer, &action)?,
120 }
121 }
122
123 match &value.flow {
124 cfg::CfgFlow::Error(_) => write_error(writer)?,
125 cfg::CfgFlow::If(ref flow) => {
126 let (offset, hole) = write_if(writer)?;
127 res.jumps.insert(offset, (hole, flow.true_target.clone()));
128 if fallthrough_next != flow.false_target.as_ref() {
129 if let Some(false_target) = flow.false_target.as_ref() {
130 let (offset, hole) = write_jump(writer)?;
131 res.jumps.insert(offset, (hole, Some(false_target.clone())));
132 } else {
133 write_raw_action(writer, &raw::Action::End)?;
134 }
135 }
136 }
137 cfg::CfgFlow::Simple(ref flow) => {
138 if fallthrough_next != flow.next.as_ref() {
139 if let Some(next) = flow.next.as_ref() {
140 let (offset, hole) = write_jump(writer)?;
141 res.jumps.insert(offset, (hole, Some(next.clone())));
142 } else {
143 write_raw_action(writer, &raw::Action::End)?;
144 }
145 }
146 }
147 cfg::CfgFlow::Return => write_raw_action(writer, &raw::Action::Return)?,
148 cfg::CfgFlow::Throw => write_raw_action(writer, &raw::Action::Throw)?,
149 cfg::CfgFlow::Try(ref flow) => {
150 write_try(writer, &mut res, flow, fallthrough_next)?;
151 }
152 cfg::CfgFlow::WaitForFrame(ref flow) => {
153 write_raw_action(
154 writer,
155 &raw::Action::WaitForFrame(raw::WaitForFrame {
156 frame: flow.frame,
157 skip: 1,
158 }),
159 )?;
160 {
161 let (offset, hole) = write_jump(writer)?;
162 res.jumps.insert(offset, (hole, flow.ready_target.clone()));
163 }
164 {
165 let (offset, hole) = write_jump(writer)?;
166 res.jumps.insert(offset, (hole, flow.loading_target.clone()));
167 }
168 }
169 cfg::CfgFlow::WaitForFrame2(ref flow) => {
170 write_raw_action(writer, &raw::Action::WaitForFrame2(raw::WaitForFrame2 { skip: 1 }))?;
171 {
172 let (offset, hole) = write_jump(writer)?;
173 res.jumps.insert(offset, (hole, flow.ready_target.clone()));
174 }
175 {
176 let (offset, hole) = write_jump(writer)?;
177 res.jumps.insert(offset, (hole, flow.loading_target.clone()));
178 }
179 }
180 cfg::CfgFlow::With(ref flow) => {
181 write_with(writer, &mut res, flow, fallthrough_next)?;
182 }
183 }
184
185 Ok(res)
186}
187
188pub fn emit_raw_action(value: &raw::Action) -> io::Result<Vec<u8>> {
189 let mut writer = PatchableBufWriter::new();
190 write_raw_action(&mut writer, value)?;
191 Ok(writer.complete())
192}
193
194fn write_raw_action(writer: &mut PatchableBufWriter, value: &raw::Action) -> io::Result<()> {
195 macro_rules! raw {
196 ($c: literal) => {{
197 emit_u8(writer, $c)?;
198 if $c >= 0x80 {
199 emit_le_u16(writer, 0)?;
200 };
201 Ok(())
202 }};
203 ($c: literal, $f: ident, $a: expr) => {{
204 debug_assert!($c >= 0x80);
205 emit_u8(writer, $c)?;
206 let hole = writer.write_hole_le_u16();
207 let body_start = writer.len();
208 $f(writer, $a)?;
209 let body_end = writer.len();
210 let body_len = body_end - body_start;
211 hole.patch(writer, body_len.try_into().unwrap());
212 Ok(())
213 }};
214 }
215
216 use raw::Action::*;
217
218 match value {
219 Add => raw!(0x0a),
220 Add2 => raw!(0x47),
221 And => raw!(0x10),
222 AsciiToChar => raw!(0x33),
223 BitAnd => raw!(0x60),
224 BitLShift => raw!(0x63),
225 BitOr => raw!(0x61),
226 BitRShift => raw!(0x64),
227 BitURShift => raw!(0x65),
228 BitXor => raw!(0x62),
229 Call => raw!(0x9e),
230 CallFunction => raw!(0x3d),
231 CallMethod => raw!(0x52),
232 CastOp => raw!(0x2b),
233 CharToAscii => raw!(0x32),
234 CloneSprite => raw!(0x24),
235 ConstantPool(ref a) => raw!(0x88, write_raw_constant_pool, a),
236 Decrement => raw!(0x51),
237 DefineFunction(ref a) => raw!(0x9b, write_raw_define_function, a),
238 DefineFunction2(ref a) => raw!(0x8e, write_raw_define_function2, a),
239 DefineLocal => raw!(0x3c),
240 DefineLocal2 => raw!(0x41),
241 Delete => raw!(0x3a),
242 Delete2 => raw!(0x3b),
243 Divide => raw!(0x0d),
244 End => raw!(0x00),
245 EndDrag => raw!(0x28),
246 Enumerate => raw!(0x46),
247 Enumerate2 => raw!(0x55),
248 Equals => raw!(0x0e),
249 Equals2 => raw!(0x49),
250 Error(_) => todo!(),
251 Extends => raw!(0x69),
252 FsCommand2 => raw!(0x2d),
253 GetMember => raw!(0x4e),
254 GetProperty => raw!(0x22),
255 GetTime => raw!(0x34),
256 GetUrl(ref a) => raw!(0x83, write_raw_get_url, a),
257 GetUrl2(ref a) => raw!(0x9a, write_raw_get_url2, a),
258 GetVariable => raw!(0x1c),
259 GotoFrame(ref a) => raw!(0x81, write_raw_goto_frame, a),
260 GotoFrame2(ref a) => raw!(0x9f, write_raw_goto_frame2, a),
261 GotoLabel(ref a) => raw!(0x8c, write_raw_goto_label, a),
262 Greater => raw!(0x67),
263 If(ref a) => raw!(0x9d, write_raw_if, a),
264 ImplementsOp => raw!(0x2c),
265 Increment => raw!(0x50),
266 InitArray => raw!(0x42),
267 InitObject => raw!(0x43),
268 InstanceOf => raw!(0x54),
269 Jump(ref a) => raw!(0x99, write_raw_jump, a),
270 Less => raw!(0x0f),
271 Less2 => raw!(0x48),
272 MbAsciiToChar => raw!(0x37),
273 MbCharToAscii => raw!(0x36),
274 MbStringExtract => raw!(0x35),
275 MbStringLength => raw!(0x31),
276 Modulo => raw!(0x3f),
277 Multiply => raw!(0x0c),
278 NewMethod => raw!(0x53),
279 NewObject => raw!(0x40),
280 NextFrame => raw!(0x04),
281 Not => raw!(0x12),
282 Or => raw!(0x11),
283 Play => raw!(0x06),
284 Pop => raw!(0x17),
285 PrevFrame => raw!(0x05),
286 Push(ref a) => raw!(0x96, write_raw_push, a),
287 PushDuplicate => raw!(0x4c),
288 RandomNumber => raw!(0x30),
289 Raw(ref a) => {
290 emit_u8(writer, a.code)?;
291 if a.code < 0x80 {
292 assert!(a.data.is_empty());
293 } else {
294 let body_len = u16::try_from(a.data.len()).unwrap();
295 emit_le_u16(writer, body_len)?;
296 writer.write_all(&a.data)?;
297 }
298 Ok(())
299 }
300 Return => raw!(0x3e),
301 RemoveSprite => raw!(0x25),
302 SetMember => raw!(0x4f),
303 SetProperty => raw!(0x23),
304 SetTarget(ref a) => raw!(0x8b, write_raw_set_target, a),
305 SetTarget2 => raw!(0x20),
306 SetVariable => raw!(0x1d),
307 StackSwap => raw!(0x4d),
308 StartDrag => raw!(0x27),
309 Stop => raw!(0x07),
310 StopSounds => raw!(0x09),
311 StoreRegister(ref a) => raw!(0x87, write_raw_store_register, a),
312 StrictEquals => raw!(0x66),
313 StrictMode(ref a) => raw!(0x89, write_raw_strict_mode, a),
314 StringAdd => raw!(0x21),
315 StringEquals => raw!(0x13),
316 StringExtract => raw!(0x15),
317 StringGreater => raw!(0x68),
318 StringLength => raw!(0x14),
319 StringLess => raw!(0x29),
320 Subtract => raw!(0x0b),
321 TargetPath => raw!(0x45),
322 Throw => raw!(0x2a),
323 ToInteger => raw!(0x18),
324 ToNumber => raw!(0x4a),
325 ToString => raw!(0x4b),
326 ToggleQuality => raw!(0x08),
327 Trace => raw!(0x26),
328 Try(ref a) => raw!(0x8f, write_raw_try, a),
329 TypeOf => raw!(0x44),
330 WaitForFrame(ref a) => raw!(0x8a, write_raw_wait_for_frame, a),
331 WaitForFrame2(ref a) => raw!(0x8d, write_raw_wait_for_frame2, a),
332 With(ref a) => raw!(0x94, write_raw_with, a),
333 }
334}
335
336fn write_raw_constant_pool<W: io::Write>(writer: &mut W, value: &raw::ConstantPool) -> io::Result<()> {
337 emit_le_u16(writer, value.pool.len().try_into().unwrap())?;
338 for constant in value.pool.iter() {
339 emit_c_string(writer, constant)?;
340 }
341 Ok(())
342}
343
344fn write_raw_define_function<W: io::Write>(writer: &mut W, value: &raw::DefineFunction) -> io::Result<()> {
345 emit_c_string(writer, &value.name)?;
346 emit_le_u16(writer, value.parameters.len().try_into().unwrap())?;
347 for parameter in value.parameters.iter() {
348 emit_c_string(writer, parameter)?;
349 }
350 emit_le_u16(writer, value.body_size)
351}
352
353fn write_raw_define_function2<W: io::Write>(writer: &mut W, value: &raw::DefineFunction2) -> io::Result<()> {
354 emit_c_string(writer, &value.name)?;
355 emit_le_u16(writer, value.parameters.len().try_into().unwrap())?;
356 emit_u8(writer, value.register_count)?;
357
358 let flags: u16 = value.flags.bits();
359 emit_le_u16(writer, flags)?;
360
361 for parameter in value.parameters.iter() {
362 emit_u8(writer, parameter.register)?;
363 emit_c_string(writer, ¶meter.name)?;
364 }
365 emit_le_u16(writer, value.body_size)
366}
367
368fn write_raw_get_url<W: io::Write>(writer: &mut W, value: &raw::GetUrl) -> io::Result<()> {
369 emit_c_string(writer, &value.url)?;
370 emit_c_string(writer, &value.target)
371}
372
373fn write_raw_get_url2<W: io::Write>(writer: &mut W, value: &raw::GetUrl2) -> io::Result<()> {
374 let method_code: u8 = match value.method {
375 GetUrl2Method::None => 0,
376 GetUrl2Method::Get => 1,
377 GetUrl2Method::Post => 2,
378 };
379 #[allow(clippy::identity_op)]
380 let flags: u8 = 0
381 | (if value.load_variables { 1 << 0 } else { 0 })
382 | (if value.load_target { 1 << 1 } else { 0 })
383 | (method_code << 6);
385 emit_u8(writer, flags)
386}
387
388fn write_raw_goto_frame<W: io::Write>(writer: &mut W, value: &raw::GotoFrame) -> io::Result<()> {
389 emit_le_u16(writer, value.frame)
390}
391
392fn write_raw_goto_frame2<W: io::Write>(writer: &mut W, value: &raw::GotoFrame2) -> io::Result<()> {
393 let has_scene_bias = value.scene_bias != 0;
394 #[allow(clippy::identity_op)]
395 let flags: u8 = 0
396 | (if value.play { 1 << 0 } else { 0 })
398 | (if has_scene_bias { 1 << 1 } else { 0 });
399 emit_u8(writer, flags)?;
401 if has_scene_bias {
402 emit_le_u16(writer, value.scene_bias)?;
403 }
404 Ok(())
405}
406
407fn write_raw_goto_label<W: io::Write>(writer: &mut W, value: &raw::GoToLabel) -> io::Result<()> {
408 emit_c_string(writer, &value.label)
409}
410
411fn write_raw_if<W: io::Write>(writer: &mut W, value: &raw::If) -> io::Result<()> {
412 emit_le_i16(writer, value.offset)
413}
414
415fn write_raw_jump<W: io::Write>(writer: &mut W, value: &raw::Jump) -> io::Result<()> {
416 emit_le_i16(writer, value.offset)
417}
418
419fn write_raw_push<W: io::Write>(writer: &mut W, value: &raw::Push) -> io::Result<()> {
420 for pushed in value.values.iter() {
421 match pushed {
422 PushValue::Boolean(v) => {
423 emit_u8(writer, 5)?;
424 emit_u8(writer, if *v { 1 } else { 0 })?;
425 }
426 PushValue::Constant(v) => match u8::try_from(*v) {
427 Ok(v) => {
428 emit_u8(writer, 8)?;
429 emit_u8(writer, v)?;
430 }
431 Err(_) => {
432 emit_u8(writer, 9)?;
433 emit_le_u16(writer, *v)?;
434 }
435 },
436 PushValue::String(v) => {
437 emit_u8(writer, 0)?;
438 emit_c_string(writer, v)?;
439 }
440 PushValue::Sint32(v) => {
441 emit_u8(writer, 7)?;
442 emit_le_i32(writer, *v)?;
443 }
444 PushValue::Float32(v) => {
445 emit_u8(writer, 1)?;
446 emit_le_f32(writer, *v)?;
447 }
448 PushValue::Float64(v) => {
449 emit_u8(writer, 6)?;
450 emit_le32_f64(writer, *v)?;
451 }
452 PushValue::Null => {
453 emit_u8(writer, 2)?;
454 }
455 PushValue::Register(v) => {
456 emit_u8(writer, 4)?;
457 emit_u8(writer, *v)?;
458 }
459 PushValue::Undefined => {
460 emit_u8(writer, 3)?;
461 }
462 };
463 }
464 Ok(())
465}
466
467fn write_raw_set_target<W: io::Write>(writer: &mut W, value: &raw::SetTarget) -> io::Result<()> {
468 emit_c_string(writer, &value.target_name)
469}
470
471fn write_raw_store_register<W: io::Write>(writer: &mut W, value: &raw::StoreRegister) -> io::Result<()> {
472 emit_u8(writer, value.register)
473}
474
475fn write_raw_strict_mode<W: io::Write>(writer: &mut W, value: &raw::StrictMode) -> io::Result<()> {
476 emit_u8(writer, if value.is_strict { 1 } else { 0 })
477}
478
479fn write_raw_try<W: io::Write>(writer: &mut W, value: &raw::Try) -> io::Result<()> {
480 let catch_in_register: bool = value
481 .catch
482 .as_ref()
483 .map(|c| matches!(c.target, CatchTarget::Register(_)))
484 .unwrap_or_default();
485 #[allow(clippy::identity_op)]
486 let flags: u8 = 0
487 | (if value.catch.is_some() { 1 << 0 } else { 0 })
488 | (if value.finally.is_some() { 1 << 1 } else { 0 })
489 | (if catch_in_register { 1 << 2 } else { 0 });
490 emit_u8(writer, flags)?;
492
493 emit_le_u16(writer, value.r#try)?;
494 emit_le_u16(writer, value.catch.as_ref().map(|c| c.size).unwrap_or(0))?;
495 emit_le_u16(writer, value.finally.unwrap_or(0))?;
496
497 if let Some(catch_target) = value.catch.as_ref().map(|c| &c.target) {
498 match catch_target {
499 CatchTarget::Register(ct) => emit_u8(writer, *ct)?,
500 CatchTarget::Variable(ct) => emit_c_string(writer, ct)?,
501 };
502 }
503 Ok(())
504}
505
506fn write_raw_wait_for_frame<W: io::Write>(writer: &mut W, value: &raw::WaitForFrame) -> io::Result<()> {
507 emit_le_u16(writer, value.frame)?;
508 emit_u8(writer, value.skip)
509}
510
511fn write_raw_wait_for_frame2<W: io::Write>(writer: &mut W, value: &raw::WaitForFrame2) -> io::Result<()> {
512 emit_u8(writer, value.skip)
513}
514
515fn write_raw_with<W: io::Write>(writer: &mut W, value: &raw::With) -> io::Result<()> {
516 emit_le_u16(writer, value.size)
517}
518
519fn write_action_header<W: io::Write>(writer: &mut W, value: ActionHeader) -> io::Result<()> {
520 emit_u8(writer, value.code)?;
521 if value.length > 0 {
522 emit_le_u16(writer, value.length)?
523 }
524 Ok(())
525}
526
527fn write_define_function(writer: &mut PatchableBufWriter, value: &cfg::DefineFunction) -> io::Result<()> {
528 let mut body: PatchableBufWriter = PatchableBufWriter::new();
529 write_hard_cfg(&mut body, &value.body, false)?;
530 write_raw_action(
531 writer,
532 &raw::Action::DefineFunction(Box::new(raw::DefineFunction {
533 name: value.name.clone(),
534 parameters: value.parameters.clone(),
535 body_size: body.len().try_into().unwrap(),
536 })),
537 )?;
538 writer.write_all(&body.complete())
539}
540
541fn write_define_function2(writer: &mut PatchableBufWriter, value: &cfg::DefineFunction2) -> io::Result<()> {
542 let mut body: PatchableBufWriter = PatchableBufWriter::new();
543 write_hard_cfg(&mut body, &value.body, false)?;
544 write_raw_action(
545 writer,
546 &raw::Action::DefineFunction2(Box::new(raw::DefineFunction2 {
547 name: value.name.clone(),
548 register_count: value.register_count,
549 flags: value.flags,
550 parameters: value.parameters.clone(),
551 body_size: body.len().try_into().unwrap(),
552 })),
553 )?;
554 writer.write_all(&body.complete())
555}
556
557fn write_error<W: io::Write>(writer: &mut W) -> io::Result<()> {
558 emit_u8(writer, 0x96)?; emit_le_u16(writer, 0x0001)?; emit_u8(writer, 0xff) }
562
563fn write_if(writer: &mut PatchableBufWriter) -> io::Result<(usize, BufferHole<i16>)> {
564 write_action_header(writer, ActionHeader { code: 0x9d, length: 2 })?;
565 let pos = writer.len();
566 let hole = writer.write_hole_le_i16();
567 Ok((pos, hole))
568}
569
570fn write_jump(writer: &mut PatchableBufWriter) -> io::Result<(usize, BufferHole<i16>)> {
571 write_action_header(writer, ActionHeader { code: 0x99, length: 2 })?;
572 let pos = writer.len();
573 let hole = writer.write_hole_le_i16();
574 Ok((pos, hole))
575}
576
577fn write_try(
578 writer: &mut PatchableBufWriter,
579 wi: &mut WriteInfo,
580 flow: &cfg::Try,
581 fallthrough_next: Option<&CfgLabel>,
582) -> io::Result<()> {
583 emit_u8(writer, 0x8f)?;
584 let action_size_hole = writer.write_hole_le_u16();
585 let action_start = writer.len();
586 let catch_in_register: bool = flow
587 .catch
588 .as_ref()
589 .map(|c| matches!(c.target, CatchTarget::Register(_)))
590 .unwrap_or_default();
591 #[allow(clippy::identity_op)]
592 let flags: u8 = 0
593 | (if flow.catch.is_some() { 1 << 0 } else { 0 })
594 | (if flow.finally.is_some() { 1 << 1 } else { 0 })
595 | (if catch_in_register { 1 << 2 } else { 0 });
596 emit_u8(writer, flags)?;
598
599 let try_size_hole = writer.write_hole_le_u16();
600 let catch_size_hole = writer.write_hole_le_u16();
601 let finally_size_hole = writer.write_hole_le_u16();
602
603 if let Some(catch_target) = flow.catch.as_ref().map(|c| &c.target) {
604 match catch_target {
605 CatchTarget::Register(ct) => emit_u8(writer, *ct)?,
606 CatchTarget::Variable(ct) => emit_c_string(writer, ct)?,
607 };
608 } else {
609 emit_u8(writer, 0)?;
610 }
611 let action_end = writer.len();
612 let action_size = u16::try_from(action_end - action_start).unwrap();
613 action_size_hole.patch(writer, action_size);
614
615 let finally_next = fallthrough_next;
616 let catch_next = flow.finally.as_ref().map(|x| &x.blocks.first().label).or(finally_next);
617 let try_next = flow
618 .catch
619 .as_ref()
620 .map(|x| &x.body.blocks.first().label)
621 .or(finally_next);
622
623 let try_wi = write_soft_cfg(writer, &flow.r#try, try_next)?;
624 wi.extend(try_wi);
625 let try_end = writer.len();
626 let try_size = u16::try_from(try_end - action_end).unwrap();
627 try_size_hole.patch(writer, try_size);
628
629 if let Some(catch) = flow.catch.as_ref() {
630 let catch_wi = write_soft_cfg(writer, &catch.body, catch_next)?;
631 wi.extend(catch_wi);
632 }
633 let catch_end = writer.len();
634 let catch_size = u16::try_from(catch_end - try_end).unwrap();
635 catch_size_hole.patch(writer, catch_size);
636
637 if let Some(finally) = flow.finally.as_ref() {
638 let finally_wi = write_soft_cfg(writer, finally, finally_next)?;
639 wi.extend(finally_wi);
640 }
641 let finally_end = writer.len();
642 let finally_size = u16::try_from(finally_end - catch_end).unwrap();
643 finally_size_hole.patch(writer, finally_size);
644
645 Ok(())
646}
647
648fn write_with(
649 writer: &mut PatchableBufWriter,
650 wi: &mut WriteInfo,
651 flow: &cfg::With,
652 fallthrough_next: Option<&CfgLabel>,
653) -> io::Result<()> {
654 write_action_header(writer, ActionHeader { code: 0x94, length: 2 })?;
655 let with_size_hole = writer.write_hole_le_u16();
656 let body_start = writer.len();
657 let with_wi = write_soft_cfg(writer, &flow.body, fallthrough_next)?;
658 let body_end = writer.len();
659 with_size_hole.patch(writer, u16::try_from(body_end - body_start).unwrap());
660 wi.extend(with_wi);
661 Ok(())
662}
663
664#[cfg(test)]
665mod tests {
666 use super::*;
667 use ::test_generator::test_resources;
668 use avm1_parser::parse_cfg;
669 use avm1_types::cfg::{Cfg, CfgFlow};
670 use std::io::Write;
671 use std::path::Path;
672
673 #[test_resources("../tests/avm1/[!.]*/*/")]
674 fn test_emit_cfg(path: &str) {
675 use serde::Serialize;
676
677 let path: &Path = Path::new(path);
678 let name_parts: Vec<&str> = path
679 .components()
680 .rev()
681 .take(2)
682 .collect::<Vec<_>>()
683 .iter()
684 .rev()
685 .map(|c| c.as_os_str().to_str().unwrap())
686 .collect();
687
688 match name_parts.join("/").as_str() {
689 "avm1-bytes/misaligned-jump" => return,
690 "samples/delta-of-dir" => return,
691 "samples/parse-data-string" => return,
692 "try/try-empty-catch-overlong-finally-err" => return,
693 "try/try-nested-return" => return,
694 "wait-for-frame/homestuck-beta2" => return,
695 "wait-for-frame/ready-increments" => return,
696 "wait-for-frame/ready-jump-increments" => return,
697 "wait-for-frame/wff2-ready-increments" => return,
698 _ => {}
699 }
700
701 let cfg_path = path.join("cfg.json");
702 let cfg_bytes: Vec<u8> = ::std::fs::read(cfg_path).expect("Failed to read input CFG");
703
704 let cfg: Cfg = ::serde_json_v8::from_slice(&cfg_bytes).expect("Failed to parse input CFG");
705
706 let actual_avm1 = emit_cfg(&cfg).expect("Failed to convert CFG to AVM1");
707
708 let actual_avm1_path = path.join("local-main.rs.avm1");
709 ::std::fs::write(actual_avm1_path, &actual_avm1).expect("Failed to write actual AVM1");
710
711 let actual_cfg = parse_cfg(&actual_avm1);
712 let actual_cfg_path = path.join("local-cfg.rs.json");
713 let actual_cfg_file = ::std::fs::File::create(actual_cfg_path).expect("Failed to create actual CFG file");
714 let actual_cfg_writer = ::std::io::BufWriter::new(actual_cfg_file);
715
716 let mut ser = serde_json_v8::Serializer::pretty(actual_cfg_writer);
717 actual_cfg.serialize(&mut ser).expect("Failed to write actual CFG");
718 ser.into_inner().write_all(b"\n").unwrap();
719
720 assert!(
721 hard_cfg_equivalent(&actual_cfg, &cfg),
722 "round-tripped CFG must be equivalent"
723 )
724 }
725
726 fn hard_cfg_equivalent(left: &Cfg, right: &Cfg) -> bool {
729 let left_labels = get_hard_cfg_labels(left);
730 let right_labels = get_hard_cfg_labels(right);
731 let left_id: HashMap<&CfgLabel, usize> = left_labels.into_iter().enumerate().map(|(i, l)| (l, i)).collect();
732 let right_id: HashMap<&CfgLabel, usize> = right_labels.into_iter().enumerate().map(|(i, l)| (l, i)).collect();
733
734 let label_eq = |left: Option<&CfgLabel>, right: Option<&CfgLabel>| -> bool {
735 match (left, right) {
736 (Some(l), Some(r)) => left_id.get(l) == right_id.get(r),
737 (l, r) => l == r,
738 }
739 };
740
741 soft_cfg_equivalent(left, right, &label_eq)
742 }
743
744 fn soft_cfg_equivalent(
745 left: &Cfg,
746 right: &Cfg,
747 label_eq: &impl Fn(Option<&CfgLabel>, Option<&CfgLabel>) -> bool,
748 ) -> bool {
749 let left_blocks = &left.blocks;
750 let right_blocks = &right.blocks;
751 if left_blocks.len() != right_blocks.len() {
752 return false;
753 }
754 for (left_block, right_block) in left_blocks.iter().zip(right_blocks.iter()) {
755 if left_block.actions.len() != right_block.actions.len() {
756 return false;
757 }
758 if !label_eq(Some(&left_block.label), Some(&right_block.label)) {
759 return false;
760 }
761 for (left_action, right_action) in left_block.actions.iter().zip(right_block.actions.iter()) {
762 if !action_equivalent(left_action, right_action) {
763 return false;
764 }
765 }
766 let flow_eq = match (&left_block.flow, &right_block.flow) {
767 (CfgFlow::If(l), CfgFlow::If(r)) => {
768 label_eq(l.true_target.as_ref(), r.true_target.as_ref())
769 && label_eq(l.false_target.as_ref(), r.false_target.as_ref())
770 }
771 (CfgFlow::Simple(l), CfgFlow::Simple(r)) => label_eq(l.next.as_ref(), r.next.as_ref()),
772 (CfgFlow::Try(l), CfgFlow::Try(r)) => try_equivalent(l, r, label_eq),
773 (CfgFlow::WaitForFrame(l), CfgFlow::WaitForFrame(r)) => {
774 l.frame == r.frame
775 && label_eq(l.ready_target.as_ref(), r.ready_target.as_ref())
776 && label_eq(l.loading_target.as_ref(), r.loading_target.as_ref())
777 }
778 (CfgFlow::WaitForFrame2(l), CfgFlow::WaitForFrame2(r)) => {
779 label_eq(l.ready_target.as_ref(), r.ready_target.as_ref())
780 && label_eq(l.loading_target.as_ref(), r.loading_target.as_ref())
781 }
782 (CfgFlow::With(l), CfgFlow::With(r)) => soft_cfg_equivalent(&l.body, &r.body, label_eq),
783 (l, r) => l == r,
784 };
785 if !flow_eq {
786 return false;
787 }
788 }
789
790 true
791 }
792
793 fn action_equivalent(left: &cfg::Action, right: &cfg::Action) -> bool {
794 match (left, right) {
795 (cfg::Action::DefineFunction(l), cfg::Action::DefineFunction(r)) => {
796 l.name == r.name && l.parameters == r.parameters && hard_cfg_equivalent(&l.body, &r.body)
797 }
798 (cfg::Action::DefineFunction2(l), cfg::Action::DefineFunction2(r)) => {
799 l.name == r.name
800 && l.register_count == r.register_count
801 && l.flags == r.flags
802 && l.parameters == r.parameters
803 && hard_cfg_equivalent(&l.body, &r.body)
804 }
805 (l, r) => l == r,
806 }
807 }
808
809 fn try_equivalent(
810 left: &cfg::Try,
811 right: &cfg::Try,
812 label_eq: &impl Fn(Option<&CfgLabel>, Option<&CfgLabel>) -> bool,
813 ) -> bool {
814 if !soft_cfg_equivalent(&left.r#try, &right.r#try, label_eq) {
815 return false;
816 }
817 let catch_eq = match (left.catch.as_ref(), right.catch.as_ref()) {
818 (Some(l), Some(r)) => l.target == r.target && soft_cfg_equivalent(&l.body, &r.body, label_eq),
819 (l, r) => l == r,
820 };
821 if !catch_eq {
822 return false;
823 }
824 match (left.finally.as_ref(), right.finally.as_ref()) {
825 (Some(l), Some(r)) => soft_cfg_equivalent(l, r, label_eq),
826 (l, r) => l == r,
827 }
828 }
829
830 fn get_hard_cfg_labels<'a>(hard_cfg: &'a Cfg) -> Vec<&'a CfgLabel> {
831 let mut result: Vec<&'a CfgLabel> = Vec::new();
832
833 fn visit<'a>(cfg: &'a Cfg, result: &mut Vec<&'a CfgLabel>) {
834 for block in cfg.blocks.iter() {
835 result.push(&block.label);
836 match &block.flow {
837 CfgFlow::Try(ref flow) => {
838 visit(&flow.r#try, result);
839 if let Some(catch) = &flow.catch {
840 visit(&catch.body, result);
841 }
842 if let Some(finally) = &flow.finally {
843 visit(finally, result);
844 }
845 }
846 CfgFlow::With(ref flow) => {
847 visit(&flow.body, result);
848 }
849 _ => {}
850 }
851 }
852 }
853
854 visit(hard_cfg, &mut result);
855
856 result
857 }
858}