1use saturnus_core::{Machine, Model};
15use serde::Serialize;
16
17use crate::error::{Error, ResultExt};
18
19type Result<T, E = Error> = std::result::Result<T, E>;
20
21use crate::decompile::Settings;
22use crate::names::NameTable;
23use crate::object::{Base, Memory, Object, Reader};
24use crate::prolog::{ObjectType, Record, records};
25
26const MAX_LEVELS: usize = 100_000;
29const MAX_REGION: u32 = 1 << 20;
31
32impl Memory for Machine {
33 fn nibble(&self, addr: u32) -> Option<u8> {
34 (addr < MAX_REGION).then(|| self.peek(addr))
35 }
36}
37
38#[derive(Clone, Copy, Debug, PartialEq, Eq)]
42pub struct Layout {
43 pub home_ptr: u32,
45 pub home_end_ptr: u32,
47 pub context_ptr: u32,
49 pub stack_ptr: u32,
51 pub stack_end_ptr: u32,
53 pub system_flags: u32,
55 pub user_flags: u32,
57 pub flag_words: usize,
59 pub menu_ptr: u32,
63}
64
65impl Layout {
66 pub const HP48SX: Layout = Layout {
68 home_ptr: 0x70592,
69 home_end_ptr: 0x70597,
70 context_ptr: 0x7059C,
71 stack_ptr: 0x70579,
72 stack_end_ptr: 0x7057E,
73 system_flags: 0x706C5,
74 user_flags: 0x706D5,
75 flag_words: 1,
76 menu_ptr: 0x7061E,
77 };
78 pub const HP48GX: Layout = Layout {
80 home_ptr: 0x80711,
81 home_end_ptr: 0x80716,
82 context_ptr: 0x8071B,
83 stack_ptr: 0x806F8,
84 stack_end_ptr: 0x806FD,
85 system_flags: 0x80843,
86 user_flags: 0x80853,
87 flag_words: 1,
88 menu_ptr: 0x8079D,
89 };
90 pub const HP49G: Layout = Layout {
92 home_ptr: 0x80711,
93 home_end_ptr: 0x80716,
94 context_ptr: 0x8071B,
95 stack_ptr: 0x806F8,
96 stack_end_ptr: 0x806FD,
97 system_flags: 0x80F02,
98 user_flags: 0x80F22,
99 flag_words: 2,
100 menu_ptr: 0x807ED,
101 };
102
103 pub fn of(model: Model) -> Option<Layout> {
106 match model {
107 Model::Hp48sx => Some(Self::HP48SX),
108 Model::Hp48gx => Some(Self::HP48GX),
109 Model::Hp49g => Some(Self::HP49G),
110 Model::Hp38g
111 | Model::Hp39g
112 | Model::Hp40g
113 | Model::Hp42s
114 | Model::Hp27s
115 | Model::Hp17bii
116 | Model::Hp17b
117 | Model::Hp28s
118 | Model::Hp19bii => None,
119 }
120 }
121}
122
123#[derive(Clone, Debug, PartialEq, Serialize)]
126pub struct Variable {
127 pub name: String,
129 #[serde(rename = "type")]
131 pub kind: String,
132 pub size: f64,
135 pub checksum: u16,
137 pub address: u32,
139 #[serde(skip_serializing_if = "Option::is_none")]
141 pub variables: Option<Vec<Variable>>,
142}
143
144#[derive(Clone, Debug, PartialEq, Eq)]
146pub struct Flags {
147 pub system: Vec<u64>,
149 pub user: Vec<u64>,
151}
152
153impl Flags {
154 pub fn get(&self, n: i32) -> Option<bool> {
157 let (words, i) = match n {
158 0 => return None,
159 n if n < 0 => (&self.system, n.unsigned_abs() - 1),
160 n => (&self.user, n.unsigned_abs() - 1),
161 };
162 let word = words.get(usize::try_from(i / 64).ok()?)?;
163 Some(word >> (i % 64) & 1 == 1)
164 }
165
166 pub fn base(&self) -> Base {
168 match (self.get(-11), self.get(-12)) {
169 (Some(true), Some(true)) => Base::Hex,
170 (Some(true), _) => Base::Oct,
171 (_, Some(true)) => Base::Bin,
172 _ => Base::Dec,
173 }
174 }
175
176 pub fn set(&self) -> Vec<i32> {
179 let mut out = Vec::new();
180 for (words, sign) in [(&self.system, -1i32), (&self.user, 1)] {
181 for (w, word) in words.iter().enumerate() {
182 for bit in 0..64 {
183 if word >> bit & 1 == 1 {
184 let n = i32::try_from(w * 64 + bit + 1).unwrap_or(i32::MAX);
185 out.push(sign * n);
186 }
187 }
188 }
189 }
190 out
191 }
192}
193
194impl Serialize for Flags {
195 fn serialize<S: serde::Serializer>(&self, s: S) -> std::result::Result<S::Ok, S::Error> {
199 use serde::ser::SerializeStruct;
200 let hex = |w: &Vec<u64>| w.iter().map(|v| format!("{v:016X}")).collect::<Vec<_>>();
201 let mut st = s.serialize_struct("Flags", 3)?;
202 st.serialize_field("system", &hex(&self.system))?;
203 st.serialize_field("user", &hex(&self.user))?;
204 st.serialize_field("set", &self.set())?;
205 st.end()
206 }
207}
208
209pub fn crc(nibbles: &[u8]) -> u16 {
212 let mut c: u32 = 0;
213 for &n in nibbles {
214 c = (c >> 4) ^ (((c ^ u32::from(n)) & 0xF) * 0x1081);
215 }
216 (c & 0xFFFF) as u16
217}
218
219#[derive(Clone, Copy)]
221pub struct UserMemory<'a> {
222 mem: &'a dyn Memory,
223 layout: Layout,
224 names: Option<&'a NameTable>,
227}
228
229impl std::fmt::Debug for UserMemory<'_> {
230 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
231 f.debug_struct("UserMemory")
232 .field("layout", &self.layout)
233 .field("names", &self.names.is_some())
234 .finish_non_exhaustive()
235 }
236}
237
238struct Home {
240 addr: u32,
242 nibbles: Vec<u8>,
244 offset_at: usize,
246}
247
248impl<'a> UserMemory<'a> {
249 pub fn new(mem: &'a dyn Memory, layout: Layout) -> Self {
251 Self {
252 mem,
253 layout,
254 names: None,
255 }
256 }
257
258 pub fn with_names(self, names: &'a NameTable) -> Self {
262 Self {
263 names: Some(names),
264 ..self
265 }
266 }
267
268 pub fn of(machine: &'a Machine) -> Result<Self> {
271 let model = machine.model();
272 let layout = Layout::of(model).ok_or(Error::NoUserMemory(model))?;
273 Ok(Self::new(machine, layout))
274 }
275
276 pub fn layout(&self) -> Layout {
278 self.layout
279 }
280
281 fn nibble(&self, addr: u32) -> Result<u8> {
282 self.mem.nibble(addr).ok_or(Error::Unreadable(addr))
283 }
284
285 fn pointer(&self, addr: u32) -> Result<u32> {
287 let mut v = 0;
288 for i in (0..5).rev() {
289 v = (v << 4) | u32::from(self.nibble(addr + i)?);
290 }
291 Ok(v)
292 }
293
294 fn region(&self, from: u32, to: u32) -> Result<Vec<u8>> {
296 if from > to || to - from > MAX_REGION {
297 return Err(Error::Memory(format!(
298 "region #{from:05X}-#{to:05X} is not plausible"
299 )));
300 }
301 (from..to).map(|a| self.nibble(a)).collect()
302 }
303
304 fn home(&self) -> Result<Home> {
305 let addr = self.pointer(self.layout.home_ptr)?;
306 let end = self.pointer(self.layout.home_end_ptr)?;
307 let nibbles = self.region(addr, end).context("reading HOME")?;
308 let prolog = crate::prolog::read_field(&nibbles, 0, 5);
309 if prolog != Some(ObjectType::Directory.prolog()) {
310 return Err(Error::NotSetUp { home: addr });
311 }
312 let count = crate::prolog::read_field(&nibbles, 5, 3)
315 .ok_or_else(|| Error::Memory("HOME truncated".into()))?;
316 let offset_at = 8 + 13 * to_usize(count);
317 Ok(Home {
318 addr,
319 nibbles,
320 offset_at,
321 })
322 }
323
324 fn variables(
328 &self,
329 n: &[u8],
330 base: u32,
331 offset_at: usize,
332 depth: usize,
333 ) -> Result<Vec<Variable>> {
334 let rs = records(n, offset_at, depth)?;
335 let mut out = Vec::with_capacity(rs.len());
336 for r in rs.iter().rev() {
337 if r.name_len == 0 {
339 continue;
340 }
341 out.push(self.variable(n, base, r, depth)?);
342 }
343 Ok(out)
344 }
345
346 fn variable(&self, n: &[u8], base: u32, r: &Record, depth: usize) -> Result<Variable> {
347 let name_bytes: Vec<u8> = (0..r.name_len)
348 .map(|i| {
349 crate::prolog::read_field(n, r.name_at + 2 + 2 * i, 2)
350 .map(|b| (b & 0xFF) as u8)
351 .ok_or_else(|| Error::Memory("name truncated".into()))
352 })
353 .collect::<Result<_>>()?;
354 let object = &n[r.object..r.end()];
355 let address = base + to_u32(r.object)?;
356 let prolog = crate::prolog::read_field(object, 0, 5)
357 .ok_or_else(|| Error::Memory("object truncated".into()))?;
358 let ty = ObjectType::from_prolog(prolog);
359 let (kind, variables) = match ty {
360 Some(ObjectType::Directory) => (
361 ty.map(ObjectType::name),
362 Some(self.variables(n, base, r.object + 8, depth + 1)?),
363 ),
364 Some(t) => (Some(t.name()), None),
365 None => (
368 self.pointer(prolog)
369 .ok()
370 .and_then(ObjectType::from_prolog)
371 .map(ObjectType::name),
372 None,
373 ),
374 };
375 Ok(Variable {
376 name: crate::charset::decode(&name_bytes),
377 kind: kind.unwrap_or("ROM Pointer").to_string(),
378 size: (r.end() - r.start) as f64 / 2.0,
379 checksum: crc(object),
380 address,
381 variables,
382 })
383 }
384
385 pub fn tree(&self) -> Result<Vec<Variable>> {
390 let home = self.home()?;
391 let vars = self.variables(&home.nibbles, home.addr, home.offset_at, 0)?;
392 let end = records(&home.nibbles, home.offset_at, 0)?
394 .last()
395 .map_or(home.offset_at + 5, Record::end);
396 if end != home.nibbles.len() {
397 return Err(Error::Memory(format!(
398 "HOME's variables end at #{:05X}, HOME at #{:05X}",
399 home.addr + to_u32(end)?,
400 home.addr + to_u32(home.nibbles.len())?
401 )));
402 }
403 Ok(vars)
404 }
405
406 pub fn current_path(&self) -> Result<Vec<String>> {
408 let context = self.pointer(self.layout.context_ptr)?;
409 let home = self.pointer(self.layout.home_ptr)?;
410 let mut path = vec!["HOME".to_string()];
411 if context == home {
412 return Ok(path);
413 }
414 if find(&self.tree()?, context, &mut path) {
415 Ok(path)
416 } else {
417 Err(Error::Memory(format!(
418 "the current directory (#{context:05X}) is not under HOME"
419 )))
420 }
421 }
422
423 pub fn stack_addresses(&self) -> Result<Vec<u32>> {
425 let top = self.pointer(self.layout.stack_ptr)?;
426 let end = self.pointer(self.layout.stack_end_ptr)?;
427 let span = end
428 .checked_sub(top)
429 .filter(|s| s % 5 == 0 && *s >= 5)
430 .ok_or_else(|| {
431 Error::Memory(format!(
432 "stack pointers #{top:05X}-#{end:05X} are not plausible"
433 ))
434 })?;
435 let depth = to_usize(span / 5 - 1);
436 if depth > MAX_LEVELS {
437 return Err(Error::Memory(format!(
438 "stack deeper than {MAX_LEVELS} levels"
439 )));
440 }
441 if self.pointer(end - 5)? != 0 {
442 return Err(Error::Memory(format!(
443 "no end marker at #{:05X}: the ROM is not idle",
444 end - 5
445 )));
446 }
447 (0..depth)
448 .map(|i| {
449 let at = top + 5 * to_u32(i)?;
450 let p = self.pointer(at)?;
451 if p == 0 {
452 return Err(Error::Memory(format!(
453 "stack level {} is 0 at #{at:05X}",
454 i + 1
455 )));
456 }
457 Ok(p)
458 })
459 .collect()
460 }
461
462 pub fn stack(&self) -> Result<Vec<Object>> {
465 let flags = self.flags()?;
466 let base = flags.base();
467 let settings = self.settings(&flags);
468 let reader = self.reader(&settings);
471 self.stack_addresses()?
472 .into_iter()
473 .enumerate()
474 .map(|(i, addr)| {
475 let mut obj = reader
476 .decode_at(addr)
477 .with_context(|| format!("stack level {}", i + 1))?;
478 obj.set_base(base);
479 Ok(obj)
480 })
481 .collect()
482 }
483
484 fn settings(&self, flags: &Flags) -> Settings {
487 let model = if self.layout == Layout::HP49G {
488 Model::Hp49g
489 } else {
490 Model::Hp48gx
491 };
492 Settings::from_flags(flags, model)
493 }
494
495 fn reader(&self, settings: &Settings) -> Reader<'a> {
496 match self.names {
497 Some(names) => Reader::with_names(self.mem, names, *settings),
498 None => Reader::new(self.mem),
499 }
500 }
501
502 pub fn object_at(&self, addr: u32) -> Result<Object> {
505 let flags = self.flags()?;
506 let mut obj = self.reader(&self.settings(&flags)).decode_at(addr)?;
507 obj.set_base(flags.base());
508 Ok(obj)
509 }
510
511 pub fn stack_described(&self) -> Result<Vec<serde_json::Value>> {
516 let settings = self.settings(&self.flags()?);
517 let mut left = crate::decompile::MAX_DESCRIBED_TEXT;
518 self.stack()?
519 .iter()
520 .map(|o| crate::decompile::described_within(o, &settings, &mut left))
521 .collect()
522 }
523
524 pub fn stack_top(&self) -> Result<(usize, Option<serde_json::Value>)> {
528 let addresses = self.stack_addresses()?;
529 let top = match addresses.first() {
530 Some(&addr) => Some(self.object_described(addr).context("stack level 1")?),
531 None => None,
532 };
533 Ok((addresses.len(), top))
534 }
535
536 pub fn object_described(&self, addr: u32) -> Result<serde_json::Value> {
539 let settings = self.settings(&self.flags()?);
540 crate::decompile::described(&self.object_at(addr)?, &settings)
541 }
542
543 pub fn edit_text_at(&self, addr: u32) -> Result<String> {
548 let flags = self.flags()?;
549 let settings = self.settings(&flags).for_editing();
550 let mut obj = self.reader(&settings).decode_at(addr)?;
551 obj.set_base(flags.base());
552 crate::decompile::edit_text(&obj, &settings)
553 }
554
555 pub fn identity_at(&self, addr: u32) -> Result<(usize, u16)> {
559 let n: Vec<u8> = (addr..MAX_REGION)
560 .map(|a| self.nibble(a))
561 .collect::<Result<_>>()?;
562 let size = crate::prolog::object_size(&n, 0)?;
563 Ok((size, crc(&n[..size])))
564 }
565
566 fn words(&self, at: u32) -> Result<Vec<u64>> {
567 (0..self.layout.flag_words)
568 .map(|w| {
569 let start = at + 16 * to_u32(w)?;
570 let mut v = 0u64;
571 for i in (0..16).rev() {
572 v = (v << 4) | u64::from(self.nibble(start + i)?);
573 }
574 Ok(v)
575 })
576 .collect()
577 }
578
579 pub fn menu_pointer(&self) -> Result<u32> {
582 self.pointer(self.layout.menu_ptr)
583 }
584
585 pub fn flags(&self) -> Result<Flags> {
587 Ok(Flags {
588 system: self.words(self.layout.system_flags)?,
589 user: self.words(self.layout.user_flags)?,
590 })
591 }
592
593 pub fn change_counter(&self) -> Result<u64> {
601 let mut h = Fnv::default();
602 let l = self.layout;
603 for p in [
604 l.home_ptr,
605 l.home_end_ptr,
606 l.context_ptr,
607 l.stack_ptr,
608 l.stack_end_ptr,
609 ] {
610 h.word(u64::from(self.pointer(p)?));
611 }
612 let home = self.pointer(l.home_ptr)?;
613 let end = self.pointer(l.home_end_ptr)?;
614 for n in self.region(home, end)? {
615 h.byte(n);
616 }
617 let top = self.pointer(l.stack_ptr)?;
619 let stack_end = self
620 .pointer(l.stack_end_ptr)?
621 .min(top + 5 * MAX_LEVELS as u32);
622 for a in (top..stack_end).step_by(5) {
623 h.word(u64::from(self.pointer(a)?));
624 }
625 let flags = self.flags()?;
626 for w in flags.system.iter().chain(&flags.user) {
627 h.word(*w);
628 }
629 Ok(h.0)
630 }
631}
632
633fn to_usize(v: u32) -> usize {
635 usize::try_from(v).unwrap_or(usize::MAX)
636}
637
638fn to_u32(v: usize) -> Result<u32> {
640 u32::try_from(v).map_err(|_| Error::Memory(format!("{v} is past the address space")))
641}
642
643fn find(vars: &[Variable], addr: u32, path: &mut Vec<String>) -> bool {
645 for v in vars {
646 let Some(children) = &v.variables else {
647 continue;
648 };
649 path.push(v.name.clone());
650 if v.address == addr || find(children, addr, path) {
651 return true;
652 }
653 path.pop();
654 }
655 false
656}
657
658struct Fnv(u64);
660
661impl Default for Fnv {
662 fn default() -> Self {
663 Fnv(0xCBF2_9CE4_8422_2325)
664 }
665}
666
667impl Fnv {
668 fn byte(&mut self, b: u8) {
669 self.0 = (self.0 ^ u64::from(b)).wrapping_mul(0x0000_0100_0000_01B3);
670 }
671
672 fn word(&mut self, w: u64) {
673 for b in w.to_le_bytes() {
674 self.byte(b);
675 }
676 }
677}
678
679pub fn memory_tree(machine: &Machine) -> Result<Vec<Variable>> {
681 UserMemory::of(machine)?.tree()
682}
683
684pub fn current_path(machine: &Machine) -> Result<Vec<String>> {
686 UserMemory::of(machine)?.current_path()
687}
688
689pub fn stack_objects(machine: &Machine) -> Result<Vec<Object>> {
691 UserMemory::of(machine)?.stack()
692}
693
694pub fn flags(machine: &Machine) -> Result<Flags> {
696 UserMemory::of(machine)?.flags()
697}
698
699pub fn change_counter(machine: &Machine) -> Result<u64> {
701 UserMemory::of(machine)?.change_counter()
702}
703
704#[cfg(test)]
705mod tests {
706 use super::*;
707
708 #[test]
711 fn models_without_a_memory_view() {
712 for model in [Model::Hp38g, Model::Hp39g, Model::Hp40g, Model::Hp42s] {
713 assert!(Layout::of(model).is_none(), "{}", model.name());
714 }
715 let m = Machine::new(Model::Hp42s, &vec![0u8; Model::Hp42s.rom_bytes()]).unwrap();
716 let e = UserMemory::of(&m).err().unwrap().to_string();
717 assert!(e.contains("the 42S has no RPL user memory"), "{e}");
718 let m = Machine::new(Model::Hp38g, &vec![0u8; Model::Hp38g.rom_bytes()]).unwrap();
719 let e = UserMemory::of(&m).err().unwrap().to_string();
720 assert!(e.contains("keeps aplets"), "{e}");
721 }
722 use crate::object::{MAX_DECODED_OBJECTS, Real, decode_at};
723
724 struct Ram(Vec<u8>);
726
727 impl Memory for Ram {
728 fn nibble(&self, addr: u32) -> Option<u8> {
729 let i = usize::try_from(addr.checked_sub(0x70000)?).ok()?;
730 self.0.get(i).copied()
731 }
732 }
733
734 impl Ram {
735 fn put(&mut self, addr: u32, hex: &str) {
736 let at = (addr - 0x70000) as usize;
737 for (i, c) in hex.chars().enumerate() {
738 self.0[at + i] = c.to_digit(16).unwrap() as u8;
739 }
740 }
741
742 fn put_ptr(&mut self, addr: u32, value: u32) {
743 let hex: String = (0..5)
744 .map(|i| format!("{:X}", value >> (4 * i) & 0xF))
745 .collect();
746 self.put(addr, &hex);
747 }
748 }
749
750 fn sx() -> Ram {
754 let mut r = Ram(vec![0; 0x10000]);
755 r.put(
756 0x7FE9B,
757 concat!(
758 "69A20200200746228D356007EFD2200000501000",
759 "00000069A20FF7C500000000C055375627B45697",
760 "37E2342534C0E4A20900006E57A2000805537562",
761 "7B45697378047A20B2130E10006014C61627D637",
762 "6047A20B2130080005094F40514255047A201932",
763 "24B2A24B2A24B2A23F2A29C2A2B2130630001085",
764 "10339201000000000000240B100020959520C2A2",
765 "09000084946100040449425144069A20FF7A0000",
766 "0000010A51033920000000000000005",
767 ),
768 );
769 let l = Layout::HP48SX;
770 r.put_ptr(l.home_ptr, 0x7FE9B);
771 r.put_ptr(l.home_end_ptr, 0x7FFFB);
772 r.put_ptr(l.context_ptr, 0x7FFCE);
773 r.put(0x71000, "C2A2090000142433920000000000000007");
775 r.put(0x71030, "E4A2051000A200000000000000");
776 r.put_ptr(0x7F000, 0x71000);
777 r.put_ptr(0x7F005, 0x7100E);
778 r.put_ptr(0x7F00A, 0x71030);
779 r.put_ptr(l.stack_ptr, 0x7F000);
780 r.put_ptr(l.stack_end_ptr, 0x7F014);
781 r.put(l.system_flags, "0FF0000008000000");
783 r.put(l.user_flags, "0400000000000000");
784 r
785 }
786
787 #[test]
788 fn tree_sizes_and_checksums_match_the_calculator() {
789 let ram = sx();
790 let m = UserMemory::new(&ram, Layout::HP48SX);
791 let tree = m.tree().unwrap();
792 let flat: Vec<(&str, &str, f64, u16)> = tree
794 .iter()
795 .map(|v| (v.name.as_str(), v.kind.as_str(), v.size, v.checksum))
796 .collect();
797 assert_eq!(
798 flat,
799 [
800 ("DIRA", "Directory", 31.0, 44093),
801 ("YY", "String", 13.5, 5889),
802 ("X", "Real Number", 16.0, 59472),
803 ("IOPAR", "List", 29.5, 8861),
804 ]
805 );
806 let dira = tree[0].variables.as_ref().unwrap();
807 assert_eq!(dira.len(), 1);
808 assert_eq!(
809 (dira[0].name.as_str(), dira[0].size, dira[0].checksum),
810 ("Z", 16.0, 23381)
811 );
812 assert_eq!(dira[0].address, 0x7FFE6);
813 assert_eq!(m.current_path().unwrap(), ["HOME", "DIRA"]);
814 assert_eq!(
815 decode_at(tree[2].address, &ram).unwrap(),
816 Object::Real {
817 value: Real::parse("42").unwrap()
818 }
819 );
820 }
821
822 #[test]
823 fn stack_and_flags() {
824 let ram = sx();
825 let m = UserMemory::new(&ram, Layout::HP48SX);
826 let flags = m.flags().unwrap();
827 assert_eq!(flags.get(-40), Some(true));
828 assert_eq!(flags.get(7), Some(true));
829 assert_eq!(flags.get(8), Some(false));
830 assert_eq!(flags.get(65), None);
831 assert_eq!(flags.base(), Base::Hex);
832 assert_eq!(flags.set(), [-5, -6, -7, -8, -9, -10, -11, -12, -40, 7]);
833 let json = serde_json::to_value(&flags).unwrap();
834 assert_eq!(json["system"][0], "0000008000000FF0");
835 assert_eq!(json["user"][0], "0000000000000040");
836 let stack = m.stack().unwrap();
837 assert_eq!(stack.len(), 3);
838 assert_eq!(stack[0], Object::String { value: "AB".into() });
839 assert_eq!(
840 stack[1],
841 Object::Real {
842 value: Real::parse("7").unwrap()
843 }
844 );
845 assert_eq!(
846 serde_json::to_value(&stack[2]).unwrap(),
847 serde_json::json!({"type": "binary", "value": 42, "base": "hex", "text": "# 2Ah"})
848 );
849 }
850
851 #[test]
852 fn change_counter_moves_with_the_tree_and_the_stack() {
853 let mut ram = sx();
854 let c0 = UserMemory::new(&ram, Layout::HP48SX)
855 .change_counter()
856 .unwrap();
857 assert_eq!(
858 UserMemory::new(&ram, Layout::HP48SX)
859 .change_counter()
860 .unwrap(),
861 c0
862 );
863 ram.put(0x7FF39 + 16, "3");
865 let c1 = UserMemory::new(&ram, Layout::HP48SX)
866 .change_counter()
867 .unwrap();
868 assert_ne!(c1, c0);
869 ram.put_ptr(Layout::HP48SX.stack_ptr, 0x7F005);
870 assert_ne!(
871 UserMemory::new(&ram, Layout::HP48SX)
872 .change_counter()
873 .unwrap(),
874 c1
875 );
876 }
877
878 fn shared_pointers(levels: u32) -> Ram {
881 let mut r = sx();
882 let base = 0x71000;
883 for i in 0..levels {
884 let a = base + 20 * i;
885 r.put(a, "47A20");
886 if i + 1 < levels {
887 r.put_ptr(a + 5, a + 20);
888 r.put_ptr(a + 10, a + 20);
889 r.put(a + 15, "B2130");
890 } else {
891 r.put(a + 5, "B2130");
892 }
893 }
894 r.put_ptr(0x7F000, base);
895 r.put_ptr(0x7F005, 0);
896 r.put_ptr(Layout::HP48SX.stack_ptr, 0x7F000);
897 r.put_ptr(Layout::HP48SX.stack_end_ptr, 0x7F00A);
898 r
899 }
900
901 #[test]
902 fn shared_pointers_hit_the_decode_budget() {
903 let ram = shared_pointers(40);
904 let m = UserMemory::new(&ram, Layout::HP48SX);
905 let calls: [&dyn Fn() -> Result<()>; 2] = [
906 &|| decode_at(0x71000, &ram).map(drop).map_err(Error::from),
907 &|| m.stack().map(drop),
908 ];
909 for call in calls {
910 let e = call().unwrap_err();
913 assert!(format!("{e:#}").contains("(MAX_DECODED_"), "{e:#}");
914 }
915 let ram = shared_pointers(12);
917 let obj = decode_at(0x71000, &ram).unwrap();
918 fn count(o: &Object) -> usize {
919 match o {
920 Object::List { items } => 1 + items.iter().map(count).sum::<usize>(),
921 _ => 1,
922 }
923 }
924 assert_eq!(count(&obj), (1 << 12) - 1);
925 assert!(count(&obj) < MAX_DECODED_OBJECTS);
926 }
927
928 fn repeated(levels: u32, hex: &str) -> Ram {
930 let mut r = sx();
931 r.put(0x71000, hex);
932 for i in 0..levels {
933 r.put_ptr(0x7F000 + 5 * i, 0x71000);
934 }
935 r.put_ptr(0x7F000 + 5 * levels, 0);
936 r.put_ptr(Layout::HP48SX.stack_ptr, 0x7F000);
937 r.put_ptr(Layout::HP48SX.stack_end_ptr, 0x7F005 + 5 * levels);
938 r
939 }
940
941 fn string(chars: usize) -> String {
943 let mut len = String::new();
944 for i in 0..5 {
945 len.push_str(&format!("{:X}", (5 + 2 * chars) >> (4 * i) & 0xF));
946 }
947 format!("C2A20{len}{}", "14".repeat(chars))
948 }
949
950 #[test]
951 fn repeated_large_objects_hit_the_nibble_budget() {
952 let ram = repeated(500, &string(20_000));
955 let m = UserMemory::new(&ram, Layout::HP48SX);
956 for e in [m.stack().unwrap_err(), m.stack_described().unwrap_err()] {
957 assert!(format!("{e:#}").contains("MAX_CLONED_NIBBLES"), "{e:#}");
958 }
959 let ram = repeated(100, &string(20_000));
962 let stack = UserMemory::new(&ram, Layout::HP48SX).stack().unwrap();
963 assert_eq!(stack.len(), 100);
964 assert_eq!(
965 stack[99],
966 Object::String {
967 value: "A".repeat(20_000)
968 }
969 );
970 }
971
972 #[test]
973 fn nested_texts_hit_the_described_budget() {
974 let hex = format!(
977 "{}{}{}",
978 "47A20".repeat(60),
979 string(2000),
980 "B2130".repeat(60)
981 );
982 let ram = repeated(100, &hex);
983 let m = UserMemory::new(&ram, Layout::HP48SX);
984 assert_eq!(m.stack().unwrap().len(), 100);
985 let e = m.stack_described().unwrap_err();
986 assert!(format!("{e:#}").contains("MAX_DESCRIBED_TEXT"), "{e:#}");
987 let ram = repeated(1, &hex);
989 let level = &UserMemory::new(&ram, Layout::HP48SX)
990 .stack_described()
991 .unwrap()[0];
992 assert_eq!(
993 level["items"][0]["text"].as_str().map(str::len),
994 Some(2000 + 59 * 4 + 2)
995 );
996 }
997
998 #[test]
999 fn broken_pointers_are_errors() {
1000 let mut ram = sx();
1001 ram.put_ptr(Layout::HP48SX.home_end_ptr, 0x7FFF0);
1002 assert!(UserMemory::new(&ram, Layout::HP48SX).tree().is_err());
1003 let mut ram = sx();
1004 ram.put_ptr(Layout::HP48SX.stack_end_ptr, 0x7F013);
1005 assert!(UserMemory::new(&ram, Layout::HP48SX).stack().is_err());
1006 ram.put_ptr(Layout::HP48SX.stack_end_ptr, 0x7F00F);
1007 assert!(UserMemory::new(&ram, Layout::HP48SX).stack().is_err());
1008 }
1009}