From d23cad322ccb7754c2945ba11f0beb9b7c799ac8 Mon Sep 17 00:00:00 2001 From: kalixtez Date: Thu, 2 Apr 2026 23:12:30 -0500 Subject: [PATCH 01/50] initial commit for serwalker --- crates/flutterdec-serwalker/Cargo.toml | 7 +++++++ crates/flutterdec-serwalker/src/lib.rs | 14 ++++++++++++++ 2 files changed, 21 insertions(+) create mode 100644 crates/flutterdec-serwalker/Cargo.toml create mode 100644 crates/flutterdec-serwalker/src/lib.rs diff --git a/crates/flutterdec-serwalker/Cargo.toml b/crates/flutterdec-serwalker/Cargo.toml new file mode 100644 index 0000000..83162e5 --- /dev/null +++ b/crates/flutterdec-serwalker/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "flutterdec-serwalker" +version.workspace = true +edition.workspace = true +license.workspace = true + +[dependencies] diff --git a/crates/flutterdec-serwalker/src/lib.rs b/crates/flutterdec-serwalker/src/lib.rs new file mode 100644 index 0000000..b93cf3f --- /dev/null +++ b/crates/flutterdec-serwalker/src/lib.rs @@ -0,0 +1,14 @@ +pub fn add(left: u64, right: u64) -> u64 { + left + right +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn it_works() { + let result = add(2, 2); + assert_eq!(result, 4); + } +} From f4ebabef2d5a72fd887999ae85a8c7a732cf3091 Mon Sep 17 00:00:00 2001 From: kalixtez Date: Thu, 2 Apr 2026 23:12:30 -0500 Subject: [PATCH 02/50] feat(serwalker): scaffold crate --- crates/flutterdec-serwalker/Cargo.toml | 7 +++++++ crates/flutterdec-serwalker/src/lib.rs | 14 ++++++++++++++ 2 files changed, 21 insertions(+) create mode 100644 crates/flutterdec-serwalker/Cargo.toml create mode 100644 crates/flutterdec-serwalker/src/lib.rs diff --git a/crates/flutterdec-serwalker/Cargo.toml b/crates/flutterdec-serwalker/Cargo.toml new file mode 100644 index 0000000..83162e5 --- /dev/null +++ b/crates/flutterdec-serwalker/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "flutterdec-serwalker" +version.workspace = true +edition.workspace = true +license.workspace = true + +[dependencies] diff --git a/crates/flutterdec-serwalker/src/lib.rs b/crates/flutterdec-serwalker/src/lib.rs new file mode 100644 index 0000000..b93cf3f --- /dev/null +++ b/crates/flutterdec-serwalker/src/lib.rs @@ -0,0 +1,14 @@ +pub fn add(left: u64, right: u64) -> u64 { + left + right +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn it_works() { + let result = add(2, 2); + assert_eq!(result, 4); + } +} From d2b3726888b9ca536e592b2ded12676b386a2b9c Mon Sep 17 00:00:00 2001 From: kalixtez Date: Sat, 4 Apr 2026 00:19:06 -0500 Subject: [PATCH 03/50] added a few macro definitions and constant declarations --- Cargo.lock | 7 ++ Cargo.toml | 1 + crates/flutterdec-serwalker/Cargo.toml | 1 + crates/flutterdec-serwalker/src/constants.rs | 14 ++++ crates/flutterdec-serwalker/src/lib.rs | 15 +---- crates/flutterdec-serwalker/src/utils.rs | 67 ++++++++++++++++++++ rust-toolchain.toml | 2 +- 7 files changed, 93 insertions(+), 14 deletions(-) create mode 100644 crates/flutterdec-serwalker/src/constants.rs create mode 100644 crates/flutterdec-serwalker/src/utils.rs diff --git a/Cargo.lock b/Cargo.lock index 6551b5a..c413b1d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -361,6 +361,13 @@ dependencies = [ "zip", ] +[[package]] +name = "flutterdec-serwalker" +version = "0.1.0-alpha.2" +dependencies = [ + "goblin", +] + [[package]] name = "foldhash" version = "0.1.5" diff --git a/Cargo.toml b/Cargo.toml index ce04ace..a529bc5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,7 @@ members = [ "crates/flutterdec-disasm-arm64", "crates/flutterdec-ir", "crates/flutterdec-decompiler", + "crates/flutterdec-serwalker", ] resolver = "2" diff --git a/crates/flutterdec-serwalker/Cargo.toml b/crates/flutterdec-serwalker/Cargo.toml index 83162e5..65fb1eb 100644 --- a/crates/flutterdec-serwalker/Cargo.toml +++ b/crates/flutterdec-serwalker/Cargo.toml @@ -5,3 +5,4 @@ edition.workspace = true license.workspace = true [dependencies] +goblin.workspace = true \ No newline at end of file diff --git a/crates/flutterdec-serwalker/src/constants.rs b/crates/flutterdec-serwalker/src/constants.rs new file mode 100644 index 0000000..7e3e408 --- /dev/null +++ b/crates/flutterdec-serwalker/src/constants.rs @@ -0,0 +1,14 @@ +use std::mem::size_of; + +pub const SNAPSHOT_MAGIC_NUMBER_SZ: usize = size_of::(); +pub const SNAPSHOT_LEN_SZ: usize = size_of::(); +pub const SNAPSHOT_KIND_SZ: usize = size_of::(); + +pub const NUM_BASE_OBJECTS_SZ: usize = size_of::(); +pub const NUM_OBJECTS_SZ: usize = size_of::(); +pub const NUM_CLUSTERS_SZ: usize = size_of::(); + +pub const INSTR_TABLE_LEN_SZ: usize = size_of::(); +pub const INSTR_TABLE_OFFSET_SZ: usize = size_of::(); + +pub const OBJECT_STORE_ENTRY_SIZE: usize = size_of::(); \ No newline at end of file diff --git a/crates/flutterdec-serwalker/src/lib.rs b/crates/flutterdec-serwalker/src/lib.rs index b93cf3f..22884e4 100644 --- a/crates/flutterdec-serwalker/src/lib.rs +++ b/crates/flutterdec-serwalker/src/lib.rs @@ -1,14 +1,3 @@ -pub fn add(left: u64, right: u64) -> u64 { - left + right -} +mod utils; +mod constants; -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn it_works() { - let result = add(2, 2); - assert_eq!(result, 4); - } -} diff --git a/crates/flutterdec-serwalker/src/utils.rs b/crates/flutterdec-serwalker/src/utils.rs new file mode 100644 index 0000000..146df36 --- /dev/null +++ b/crates/flutterdec-serwalker/src/utils.rs @@ -0,0 +1,67 @@ +use std::vec; + +struct DataSnapshot +{ + clusters: Vec<&'static mut dyn Cluster>, + + num_base_objects: u64, + num_objects: u64, + num_clusters: u64, + + instr_table_len: u64, + instr_table_offset: u64, +} +trait Cluster +{ + fn is_fixed_len(&self) -> bool; + fn get_size(&self) -> usize; +} + +macro_rules! DECLARE_FIXED_LENGTH_CLUSTER +{ + ($name:ident, $instance_size:literal) => { + struct $name + { + tags: u32, + count: u64 + } + + impl Cluster for $name + { + fn get_size(&self) -> usize + { + $instance_size + } + + fn is_fixed_len(&self) -> bool + { + true + } + } + }; +} + +macro_rules! DECLARE_VARIABLE_LENGTH_CLUSTER +{ + ($name:ident, $get_length_impl:block) => { + struct $name + { + tags: u32, + count: u64 + } + + impl Cluster for $name + { + fn get_size(&self) -> usize + $get_length_impl + + fn is_fixed_len(&self) -> bool + { + false + } + } + }; +} + +DECLARE_FIXED_LENGTH_CLUSTER!(OneByteStringCluster, 16); +DECLARE_FIXED_LENGTH_CLUSTER!(TwoByteStringCluster, 16); diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 73cb934..3fe7418 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,3 +1,3 @@ [toolchain] channel = "stable" -components = ["rustfmt", "clippy"] +components = ["rustfmt", "clippy", "rust-analyzer"] From f2049a0d9f651d9fbcf28e1a2f863046b3a631b9 Mon Sep 17 00:00:00 2001 From: kalixtez Date: Sat, 4 Apr 2026 00:19:06 -0500 Subject: [PATCH 04/50] feat(serwalker): add macro and constant declarations --- Cargo.lock | 7 ++ Cargo.toml | 1 + crates/flutterdec-serwalker/Cargo.toml | 1 + crates/flutterdec-serwalker/src/constants.rs | 14 ++++ crates/flutterdec-serwalker/src/lib.rs | 15 +---- crates/flutterdec-serwalker/src/utils.rs | 67 ++++++++++++++++++++ rust-toolchain.toml | 2 +- 7 files changed, 93 insertions(+), 14 deletions(-) create mode 100644 crates/flutterdec-serwalker/src/constants.rs create mode 100644 crates/flutterdec-serwalker/src/utils.rs diff --git a/Cargo.lock b/Cargo.lock index 6551b5a..c413b1d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -361,6 +361,13 @@ dependencies = [ "zip", ] +[[package]] +name = "flutterdec-serwalker" +version = "0.1.0-alpha.2" +dependencies = [ + "goblin", +] + [[package]] name = "foldhash" version = "0.1.5" diff --git a/Cargo.toml b/Cargo.toml index ce04ace..a529bc5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,7 @@ members = [ "crates/flutterdec-disasm-arm64", "crates/flutterdec-ir", "crates/flutterdec-decompiler", + "crates/flutterdec-serwalker", ] resolver = "2" diff --git a/crates/flutterdec-serwalker/Cargo.toml b/crates/flutterdec-serwalker/Cargo.toml index 83162e5..65fb1eb 100644 --- a/crates/flutterdec-serwalker/Cargo.toml +++ b/crates/flutterdec-serwalker/Cargo.toml @@ -5,3 +5,4 @@ edition.workspace = true license.workspace = true [dependencies] +goblin.workspace = true \ No newline at end of file diff --git a/crates/flutterdec-serwalker/src/constants.rs b/crates/flutterdec-serwalker/src/constants.rs new file mode 100644 index 0000000..7e3e408 --- /dev/null +++ b/crates/flutterdec-serwalker/src/constants.rs @@ -0,0 +1,14 @@ +use std::mem::size_of; + +pub const SNAPSHOT_MAGIC_NUMBER_SZ: usize = size_of::(); +pub const SNAPSHOT_LEN_SZ: usize = size_of::(); +pub const SNAPSHOT_KIND_SZ: usize = size_of::(); + +pub const NUM_BASE_OBJECTS_SZ: usize = size_of::(); +pub const NUM_OBJECTS_SZ: usize = size_of::(); +pub const NUM_CLUSTERS_SZ: usize = size_of::(); + +pub const INSTR_TABLE_LEN_SZ: usize = size_of::(); +pub const INSTR_TABLE_OFFSET_SZ: usize = size_of::(); + +pub const OBJECT_STORE_ENTRY_SIZE: usize = size_of::(); \ No newline at end of file diff --git a/crates/flutterdec-serwalker/src/lib.rs b/crates/flutterdec-serwalker/src/lib.rs index b93cf3f..22884e4 100644 --- a/crates/flutterdec-serwalker/src/lib.rs +++ b/crates/flutterdec-serwalker/src/lib.rs @@ -1,14 +1,3 @@ -pub fn add(left: u64, right: u64) -> u64 { - left + right -} +mod utils; +mod constants; -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn it_works() { - let result = add(2, 2); - assert_eq!(result, 4); - } -} diff --git a/crates/flutterdec-serwalker/src/utils.rs b/crates/flutterdec-serwalker/src/utils.rs new file mode 100644 index 0000000..146df36 --- /dev/null +++ b/crates/flutterdec-serwalker/src/utils.rs @@ -0,0 +1,67 @@ +use std::vec; + +struct DataSnapshot +{ + clusters: Vec<&'static mut dyn Cluster>, + + num_base_objects: u64, + num_objects: u64, + num_clusters: u64, + + instr_table_len: u64, + instr_table_offset: u64, +} +trait Cluster +{ + fn is_fixed_len(&self) -> bool; + fn get_size(&self) -> usize; +} + +macro_rules! DECLARE_FIXED_LENGTH_CLUSTER +{ + ($name:ident, $instance_size:literal) => { + struct $name + { + tags: u32, + count: u64 + } + + impl Cluster for $name + { + fn get_size(&self) -> usize + { + $instance_size + } + + fn is_fixed_len(&self) -> bool + { + true + } + } + }; +} + +macro_rules! DECLARE_VARIABLE_LENGTH_CLUSTER +{ + ($name:ident, $get_length_impl:block) => { + struct $name + { + tags: u32, + count: u64 + } + + impl Cluster for $name + { + fn get_size(&self) -> usize + $get_length_impl + + fn is_fixed_len(&self) -> bool + { + false + } + } + }; +} + +DECLARE_FIXED_LENGTH_CLUSTER!(OneByteStringCluster, 16); +DECLARE_FIXED_LENGTH_CLUSTER!(TwoByteStringCluster, 16); diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 73cb934..3fe7418 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,3 +1,3 @@ [toolchain] channel = "stable" -components = ["rustfmt", "clippy"] +components = ["rustfmt", "clippy", "rust-analyzer"] From 573351768a900e19d54bc210279b30bcfe7e6969 Mon Sep 17 00:00:00 2001 From: kalixtez Date: Mon, 13 Apr 2026 00:26:28 -0500 Subject: [PATCH 05/50] add common stream methods --- crates/flutterdec-serwalker/src/constants.rs | 11 ++++++++- crates/flutterdec-serwalker/src/stream.rs | 20 +++++++++++++++ crates/flutterdec-serwalker/src/utils.rs | 26 ++++++++++++++++---- 3 files changed, 51 insertions(+), 6 deletions(-) create mode 100644 crates/flutterdec-serwalker/src/stream.rs diff --git a/crates/flutterdec-serwalker/src/constants.rs b/crates/flutterdec-serwalker/src/constants.rs index 7e3e408..b896046 100644 --- a/crates/flutterdec-serwalker/src/constants.rs +++ b/crates/flutterdec-serwalker/src/constants.rs @@ -1,6 +1,11 @@ use std::mem::size_of; pub const SNAPSHOT_MAGIC_NUMBER_SZ: usize = size_of::(); + +pub const END_OF_ULEB_MASK: u8 = 0x80; // last byte +pub const ULEB_EXTRACT_BYTE_DATA_MASK: u8 = 0x7f; // more bytes to follow + +/* pub const SNAPSHOT_LEN_SZ: usize = size_of::(); pub const SNAPSHOT_KIND_SZ: usize = size_of::(); @@ -11,4 +16,8 @@ pub const NUM_CLUSTERS_SZ: usize = size_of::(); pub const INSTR_TABLE_LEN_SZ: usize = size_of::(); pub const INSTR_TABLE_OFFSET_SZ: usize = size_of::(); -pub const OBJECT_STORE_ENTRY_SIZE: usize = size_of::(); \ No newline at end of file +pub const CLUSTER_TAGS_SZ: usize = size_of::(); +pub const CLUSTER_OBJ_COUNT_SZ: usize = size_of::(); + +pub const OBJECT_STORE_ENTRY_SIZE: usize = size_of::(); +*/ \ No newline at end of file diff --git a/crates/flutterdec-serwalker/src/stream.rs b/crates/flutterdec-serwalker/src/stream.rs new file mode 100644 index 0000000..d38fb0b --- /dev/null +++ b/crates/flutterdec-serwalker/src/stream.rs @@ -0,0 +1,20 @@ +mod constants; + +struct Stream +{ + byte_stream: &[u8], + curr_stream_offset: usize, +} + +impl Stream +{ + fn seek(&self, pos: usize) + { + self.curr_stream_offset = pos; + } + + fn read_modified_uleb128(stream: &[u8]) // read a modified uleb in the current poss + { + let bytes = Vec + } +} \ No newline at end of file diff --git a/crates/flutterdec-serwalker/src/utils.rs b/crates/flutterdec-serwalker/src/utils.rs index 146df36..72bd0fb 100644 --- a/crates/flutterdec-serwalker/src/utils.rs +++ b/crates/flutterdec-serwalker/src/utils.rs @@ -1,9 +1,15 @@ -use std::vec; struct DataSnapshot { clusters: Vec<&'static mut dyn Cluster>, - + + magic_bytes: u32, + size: u64, + kind: u64, + + version: String, + features: String, + num_base_objects: u64, num_objects: u64, num_clusters: u64, @@ -23,7 +29,7 @@ macro_rules! DECLARE_FIXED_LENGTH_CLUSTER struct $name { tags: u32, - count: u64 + count: u64, } impl Cluster for $name @@ -63,5 +69,15 @@ macro_rules! DECLARE_VARIABLE_LENGTH_CLUSTER }; } -DECLARE_FIXED_LENGTH_CLUSTER!(OneByteStringCluster, 16); -DECLARE_FIXED_LENGTH_CLUSTER!(TwoByteStringCluster, 16); +// These are the objects that call ReadAllocFixedSize during deserialization, +// whose fill cluster size is uniquely determined by sizeof(Object) * num_of_objects +// and alloc cluster size is tags (u32) + num_of_objects (ULEB128) + +DECLARE_FIXED_LENGTH_CLUSTER!(OneByteStringCluster, 2); +DECLARE_FIXED_LENGTH_CLUSTER!(TwoByteStringCluster, 8); +DECLARE_FIXED_LENGTH_CLUSTER!(StringCluster, 8); +DECLARE_FIXED_LENGTH_CLUSTER!(MintCluster, 16); +DECLARE_FIXED_LENGTH_CLUSTER!(DoubleCluster, 16); +DECLARE_FIXED_LENGTH_CLUSTER!(TypeParameterCluster, 32); +DECLARE_FIXED_LENGTH_CLUSTER!(TypeCluster, 32); +DECLARE_FIXED_LENGTH_CLUSTER!(TypeArgumentsCluster, 32); From 89135839ffa6b6fd9a428dcae6a547a47a04946d Mon Sep 17 00:00:00 2001 From: kalixtez Date: Mon, 13 Apr 2026 00:26:28 -0500 Subject: [PATCH 06/50] feat(serwalker): add common stream methods --- crates/flutterdec-serwalker/src/constants.rs | 11 ++++++++- crates/flutterdec-serwalker/src/stream.rs | 20 +++++++++++++++ crates/flutterdec-serwalker/src/utils.rs | 26 ++++++++++++++++---- 3 files changed, 51 insertions(+), 6 deletions(-) create mode 100644 crates/flutterdec-serwalker/src/stream.rs diff --git a/crates/flutterdec-serwalker/src/constants.rs b/crates/flutterdec-serwalker/src/constants.rs index 7e3e408..b896046 100644 --- a/crates/flutterdec-serwalker/src/constants.rs +++ b/crates/flutterdec-serwalker/src/constants.rs @@ -1,6 +1,11 @@ use std::mem::size_of; pub const SNAPSHOT_MAGIC_NUMBER_SZ: usize = size_of::(); + +pub const END_OF_ULEB_MASK: u8 = 0x80; // last byte +pub const ULEB_EXTRACT_BYTE_DATA_MASK: u8 = 0x7f; // more bytes to follow + +/* pub const SNAPSHOT_LEN_SZ: usize = size_of::(); pub const SNAPSHOT_KIND_SZ: usize = size_of::(); @@ -11,4 +16,8 @@ pub const NUM_CLUSTERS_SZ: usize = size_of::(); pub const INSTR_TABLE_LEN_SZ: usize = size_of::(); pub const INSTR_TABLE_OFFSET_SZ: usize = size_of::(); -pub const OBJECT_STORE_ENTRY_SIZE: usize = size_of::(); \ No newline at end of file +pub const CLUSTER_TAGS_SZ: usize = size_of::(); +pub const CLUSTER_OBJ_COUNT_SZ: usize = size_of::(); + +pub const OBJECT_STORE_ENTRY_SIZE: usize = size_of::(); +*/ \ No newline at end of file diff --git a/crates/flutterdec-serwalker/src/stream.rs b/crates/flutterdec-serwalker/src/stream.rs new file mode 100644 index 0000000..d38fb0b --- /dev/null +++ b/crates/flutterdec-serwalker/src/stream.rs @@ -0,0 +1,20 @@ +mod constants; + +struct Stream +{ + byte_stream: &[u8], + curr_stream_offset: usize, +} + +impl Stream +{ + fn seek(&self, pos: usize) + { + self.curr_stream_offset = pos; + } + + fn read_modified_uleb128(stream: &[u8]) // read a modified uleb in the current poss + { + let bytes = Vec + } +} \ No newline at end of file diff --git a/crates/flutterdec-serwalker/src/utils.rs b/crates/flutterdec-serwalker/src/utils.rs index 146df36..72bd0fb 100644 --- a/crates/flutterdec-serwalker/src/utils.rs +++ b/crates/flutterdec-serwalker/src/utils.rs @@ -1,9 +1,15 @@ -use std::vec; struct DataSnapshot { clusters: Vec<&'static mut dyn Cluster>, - + + magic_bytes: u32, + size: u64, + kind: u64, + + version: String, + features: String, + num_base_objects: u64, num_objects: u64, num_clusters: u64, @@ -23,7 +29,7 @@ macro_rules! DECLARE_FIXED_LENGTH_CLUSTER struct $name { tags: u32, - count: u64 + count: u64, } impl Cluster for $name @@ -63,5 +69,15 @@ macro_rules! DECLARE_VARIABLE_LENGTH_CLUSTER }; } -DECLARE_FIXED_LENGTH_CLUSTER!(OneByteStringCluster, 16); -DECLARE_FIXED_LENGTH_CLUSTER!(TwoByteStringCluster, 16); +// These are the objects that call ReadAllocFixedSize during deserialization, +// whose fill cluster size is uniquely determined by sizeof(Object) * num_of_objects +// and alloc cluster size is tags (u32) + num_of_objects (ULEB128) + +DECLARE_FIXED_LENGTH_CLUSTER!(OneByteStringCluster, 2); +DECLARE_FIXED_LENGTH_CLUSTER!(TwoByteStringCluster, 8); +DECLARE_FIXED_LENGTH_CLUSTER!(StringCluster, 8); +DECLARE_FIXED_LENGTH_CLUSTER!(MintCluster, 16); +DECLARE_FIXED_LENGTH_CLUSTER!(DoubleCluster, 16); +DECLARE_FIXED_LENGTH_CLUSTER!(TypeParameterCluster, 32); +DECLARE_FIXED_LENGTH_CLUSTER!(TypeCluster, 32); +DECLARE_FIXED_LENGTH_CLUSTER!(TypeArgumentsCluster, 32); From dd1ecfdf7e6eb4dcb6d9e5e1279890e122a53a5f Mon Sep 17 00:00:00 2001 From: kalixtez Date: Mon, 27 Apr 2026 14:24:37 -0500 Subject: [PATCH 07/50] Implemented a few stream-reading functions, implemented the base struct for fixed-alloc data types, added the ClassId enum to uniquely distinguish all class ids --- Cargo.lock | 7 + crates/flutterdec-serwalker/Cargo.toml | 3 +- .../flutterdec-serwalker/src/cluster/mod.rs | 56 +++++ crates/flutterdec-serwalker/src/constants.rs | 207 ++++++++++++++++- crates/flutterdec-serwalker/src/lib.rs | 5 + .../src/raw_object/mod.rs | 214 ++++++++++++++++++ crates/flutterdec-serwalker/src/snapshot.rs | 84 +++++++ crates/flutterdec-serwalker/src/stream.rs | 109 ++++++++- crates/flutterdec-serwalker/src/utils.rs | 107 ++++----- 9 files changed, 710 insertions(+), 82 deletions(-) create mode 100644 crates/flutterdec-serwalker/src/cluster/mod.rs create mode 100644 crates/flutterdec-serwalker/src/raw_object/mod.rs create mode 100644 crates/flutterdec-serwalker/src/snapshot.rs diff --git a/Cargo.lock b/Cargo.lock index c413b1d..6482e4e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -366,6 +366,7 @@ name = "flutterdec-serwalker" version = "0.1.0-alpha.2" dependencies = [ "goblin", + "paste", ] [[package]] @@ -571,6 +572,12 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + [[package]] name = "plain" version = "0.2.3" diff --git a/crates/flutterdec-serwalker/Cargo.toml b/crates/flutterdec-serwalker/Cargo.toml index 65fb1eb..1885862 100644 --- a/crates/flutterdec-serwalker/Cargo.toml +++ b/crates/flutterdec-serwalker/Cargo.toml @@ -5,4 +5,5 @@ edition.workspace = true license.workspace = true [dependencies] -goblin.workspace = true \ No newline at end of file +goblin.workspace = true +paste = "1.0.15" diff --git a/crates/flutterdec-serwalker/src/cluster/mod.rs b/crates/flutterdec-serwalker/src/cluster/mod.rs new file mode 100644 index 0000000..568bdb5 --- /dev/null +++ b/crates/flutterdec-serwalker/src/cluster/mod.rs @@ -0,0 +1,56 @@ +use crate::{constants, stream::Stream}; +use crate::DECLARE_FIXED_LENGTH_CLUSTER; +use crate::raw_object::*; +use crate::constants::{ClassId, ClassId::*, SIGNED_M, UNSIGNED_M}; + +type Smi = i32; + +pub trait Cluster +{ + fn is_fixed_len(&self) -> bool; + fn read_alloc(&mut self, last_ref_id: &mut u64, stream: &mut Stream) -> usize; + fn read_fill(&mut self, last_ref_id: &mut u64, stream: &mut Stream) -> usize; +} + +pub fn read_cluster_alloc() +{ + let curr_ref_id: u64 = 0; +} + +pub fn read_cluster_fill() +{ + let curr_ref_id: u64 = 0; +} + +pub fn read_and_decompress_smi(stream: &mut Stream) -> Smi +{ + let raw_smi = stream.read_modified_leb128(UNSIGNED_M); // smis are always written as signed + + (raw_smi as Smi) >> constants::SMI_SHIFT +} + +pub fn decide_cluster(clusters: &mut [Box; constants::MAX_CLUSTER_NUM], class_id: ClassId) -> Result, &str> +{ + match class_id { + IllegalCid => Err("Not a supported class (illegal class)..."), + _ => Err("Not a supported class..."), + } +} + +// These are the objects that call ReadAllocFixedSize during deserialization, +// whose fill cluster size is uniquely determined by sizeof(Object) * num_of_objects +// and alloc cluster size is tags (MULEB128) + num_of_objects (MULEB128) + + +DECLARE_FIXED_LENGTH_CLUSTER!(OneByteString, { 1 + // to-do +}); +/* +DECLARE_FIXED_LENGTH_CLUSTER!(TwoByteString, 8); +DECLARE_FIXED_LENGTH_CLUSTER!(String, 8); +DECLARE_FIXED_LENGTH_CLUSTER!(Mint, 16); +DECLARE_FIXED_LENGTH_CLUSTER!(Double, 16); +DECLARE_FIXED_LENGTH_CLUSTER!(TypeParameter, 32); +DECLARE_FIXED_LENGTH_CLUSTER!(Type, 32); +DECLARE_FIXED_LENGTH_CLUSTER!(TypeArguments, 32); +*/ \ No newline at end of file diff --git a/crates/flutterdec-serwalker/src/constants.rs b/crates/flutterdec-serwalker/src/constants.rs index b896046..63b35c6 100644 --- a/crates/flutterdec-serwalker/src/constants.rs +++ b/crates/flutterdec-serwalker/src/constants.rs @@ -1,13 +1,212 @@ use std::mem::size_of; +pub const MAGIC_BYTES: u32 = 0xdcdcf5f5; + pub const SNAPSHOT_MAGIC_NUMBER_SZ: usize = size_of::(); +pub const SNAPSHOT_LEN_SZ: usize = size_of::(); +pub const SNAPSHOT_KIND_SZ: usize = size_of::(); + +pub const SNAPSHOT_HEADER_SZ: usize = SNAPSHOT_MAGIC_NUMBER_SZ // 20 bytes of header + + SNAPSHOT_LEN_SZ + + SNAPSHOT_KIND_SZ; + +pub const MAX_CLUSTER_NUM: usize = 67usize; + +pub const UNSIGNED_END_OF_DATA_BYTE: u8 = 0x80u8; // last byte +pub const UNSIGNED_MAX_DATA_PER_BYTE: u8 = 0x7fu8; // more bytes to follow (for both) + +pub const SIGNED_END_OF_DATA_BYTE: u8 = 0xc0u8; // last byte -pub const END_OF_ULEB_MASK: u8 = 0x80; // last byte -pub const ULEB_EXTRACT_BYTE_DATA_MASK: u8 = 0x7f; // more bytes to follow +pub const SIGNED_M: u8 = SIGNED_END_OF_DATA_BYTE; +pub const UNSIGNED_M: u8 = UNSIGNED_END_OF_DATA_BYTE; + +pub const DATA_BITS_PER_BYTE: usize = 7usize; + +pub const SMI_SHIFT: usize = 1usize; + +pub const VERSION_HASH_LENGTH: usize = 32usize; + +pub enum ClassId { + IllegalCid = 0, + NativePointer, + FreeListElement, + ForwardingCorpse, + ObjectCid, + ClassCid, + PatchClassCid, + FunctionCid, + TypeParametersCid, + ClosureDataCid, + FfiTrampolineDataCid, + FieldCid, + ScriptCid, + LibraryCid, + NamespaceCid, + KernelProgramInfoCid, + WeakSerializationReferenceCid, + WeakArrayCid, + CodeCid, + BytecodeCid, + InstructionsCid, + InstructionsSectionCid, + InstructionsTableCid, + ObjectPoolCid, + PcDescriptorsCid, + CodeSourceMapCid, + CompressedStackMapsCid, + LocalVarDescriptorsCid, + ExceptionHandlersCid, + ContextCid, + ContextScopeCid, + SentinelCid, + SingleTargetCacheCid, + MonomorphicSmiableCallCid, + CallSiteDataCid, + UnlinkedCallCid, + ICDataCid, + MegamorphicCacheCid, + SubtypeTestCacheCid, + LoadingUnitCid, + ErrorCid, + ApiErrorCid, + LanguageErrorCid, + UnhandledExceptionCid, + UnwindErrorCid, + InstanceCid, + LibraryPrefixCid, + TypeArgumentsCid, + AbstractTypeCid, + TypeCid, + FunctionTypeCid, + RecordTypeCid, + TypeParameterCid, + FinalizerBaseCid, + FinalizerCid, + NativeFinalizerCid, + FinalizerEntryCid, + ClosureCid, + NumberCid, + IntegerCid, + SmiCid, + MintCid, + DoubleCid, + BoolCid, + Float32x4Cid, + Int32x4Cid, + Float64x2Cid, + RecordCid, + TypedDataBaseCid, + TypedDataCid, + ExternalTypedDataCid, + TypedDataViewCid, + PointerCid, + DynamicLibraryCid, + CapabilityCid, + ReceivePortCid, + SendPortCid, + StackTraceCid, + SuspendStateCid, + RegExpCid, + WeakPropertyCid, + WeakReferenceCid, + MirrorReferenceCid, + FutureOrCid, + UserTagCid, + TransferableTypedDataCid, + MapCid, + ConstMapCid, + SetCid, + ConstSetCid, + ArrayCid, + ImmutableArrayCid, + GrowableObjectArrayCid, + StringCid, + OneByteStringCid, + TwoByteStringCid, + FfiNativeFunctionCid, + FfiInt8Cid, + FfiInt16Cid, + FfiInt32Cid, + FfiInt64Cid, + FfiUint8Cid, + FfiUint16Cid, + FfiUint32Cid, + FfiUint64Cid, + FfiFloatCid, + FfiDoubleCid, + FfiVoidCid, + FfiHandleCid, + FfiBoolCid, + FfiNativeTypeCid, + FfiStructCid, + TypedDataInt8ArrayCid, + TypedDataInt8ArrayViewCid, + ExternalTypedDataInt8ArrayCid, + UnmodifiableTypedDataInt8ArrayViewCid, + TypedDataUint8ArrayCid, + TypedDataUint8ArrayViewCid, + ExternalTypedDataUint8ArrayCid, + UnmodifiableTypedDataUint8ArrayViewCid, + TypedDataUint8ClampedArrayCid, + TypedDataUint8ClampedArrayViewCid, + ExternalTypedDataUint8ClampedArrayCid, + UnmodifiableTypedDataUint8ClampedArrayViewCid, + TypedDataInt16ArrayCid, + TypedDataInt16ArrayViewCid, + ExternalTypedDataInt16ArrayCid, + UnmodifiableTypedDataInt16ArrayViewCid, + TypedDataUint16ArrayCid, + TypedDataUint16ArrayViewCid, + ExternalTypedDataUint16ArrayCid, + UnmodifiableTypedDataUint16ArrayViewCid, + TypedDataInt32ArrayCid, + TypedDataInt32ArrayViewCid, + ExternalTypedDataInt32ArrayCid, + UnmodifiableTypedDataInt32ArrayViewCid, + TypedDataUint32ArrayCid, + TypedDataUint32ArrayViewCid, + ExternalTypedDataUint32ArrayCid, + UnmodifiableTypedDataUint32ArrayViewCid, + TypedDataInt64ArrayCid, + TypedDataInt64ArrayViewCid, + ExternalTypedDataInt64ArrayCid, + UnmodifiableTypedDataInt64ArrayViewCid, + TypedDataUint64ArrayCid, + TypedDataUint64ArrayViewCid, + ExternalTypedDataUint64ArrayCid, + UnmodifiableTypedDataUint64ArrayViewCid, + TypedDataFloat32ArrayCid, + TypedDataFloat32ArrayViewCid, + ExternalTypedDataFloat32ArrayCid, + UnmodifiableTypedDataFloat32ArrayViewCid, + TypedDataFloat64ArrayCid, + TypedDataFloat64ArrayViewCid, + ExternalTypedDataFloat64ArrayCid, + UnmodifiableTypedDataFloat64ArrayViewCid, + TypedDataFloat32x4ArrayCid, + TypedDataFloat32x4ArrayViewCid, + ExternalTypedDataFloat32x4ArrayCid, + UnmodifiableTypedDataFloat32x4ArrayViewCid, + TypedDataInt32x4ArrayCid, + TypedDataInt32x4ArrayViewCid, + ExternalTypedDataInt32x4ArrayCid, + UnmodifiableTypedDataInt32x4ArrayViewCid, + TypedDataFloat64x2ArrayCid, + TypedDataFloat64x2ArrayViewCid, + ExternalTypedDataFloat64x2ArrayCid, + UnmodifiableTypedDataFloat64x2ArrayViewCid, + ByteDataViewCid, + UnmodifiableByteDataViewCid, + ByteBufferCid, + NullCid, + DynamicCid, + VoidCid, + NeverCid, + NumPredefinedCids, +} /* -pub const SNAPSHOT_LEN_SZ: usize = size_of::(); -pub const SNAPSHOT_KIND_SZ: usize = size_of::(); + pub const NUM_BASE_OBJECTS_SZ: usize = size_of::(); pub const NUM_OBJECTS_SZ: usize = size_of::(); diff --git a/crates/flutterdec-serwalker/src/lib.rs b/crates/flutterdec-serwalker/src/lib.rs index 22884e4..1f91630 100644 --- a/crates/flutterdec-serwalker/src/lib.rs +++ b/crates/flutterdec-serwalker/src/lib.rs @@ -1,3 +1,8 @@ mod utils; mod constants; +mod cluster; +mod raw_object; +mod stream; + +mod snapshot; \ No newline at end of file diff --git a/crates/flutterdec-serwalker/src/raw_object/mod.rs b/crates/flutterdec-serwalker/src/raw_object/mod.rs new file mode 100644 index 0000000..3ac4da2 --- /dev/null +++ b/crates/flutterdec-serwalker/src/raw_object/mod.rs @@ -0,0 +1,214 @@ +// raw_object/mod.rs + +type Smi = i64; // Using i64 for Smi fields to be decompressed + +// --- Fixed-Size Objects with defined fields --- + +pub struct Mint { + pub value: i64, +} + +pub struct Double { + pub value: f64, +} + +pub struct TypeArguments<'a> { + pub instantiations: Option<&'a mut Array<'a>>, // ArrayPtr + pub length: Smi, // Smi + pub hash: Smi, // Smi + pub nullability: Smi, // Smi +} + +pub struct TypeParameter<'a> { + pub owner: Option<&'a mut Object<'a>>, // ObjectPtr + pub base: i16, + pub index: i16, +} + +pub struct Type<'a> { + pub arguments: Option<&'a mut TypeArguments<'a>>, // TypeArgumentsPtr +} + +pub struct TypeParameters<'a> { + pub names: Option<&'a mut Array<'a>>, // ArrayPtr + pub flags: Option<&'a mut Array<'a>>, // ArrayPtr + pub bounds: Option<&'a mut TypeArguments<'a>>, // TypeArgumentsPtr + pub defaults: Option<&'a mut TypeArguments<'a>>, // TypeArgumentsPtr +} + +pub struct PatchClass<'a> { + pub wrapped_class: Option<&'a mut Class<'a>>, // ClassPtr + pub script: Option<&'a mut Script<'a>>, // ScriptPtr + pub kernel_program_info: Option<&'a mut KernelProgramInfo<'a>>, // KernelProgramInfoPtr +} + +pub struct ClosureData<'a> { + pub context_scope: Option<&'a mut ContextScope>, // ContextScopePtr + pub parent_function: Option<&'a mut Function<'a>>, // FunctionPtr + pub closure: Option<&'a mut Closure<'a>>, // ClosurePtr + pub packed_fields: u32, +} + +pub struct FfiTrampolineData<'a> { + pub signature_type: Option<&'a mut Type<'a>>, // TypePtr + pub c_signature: Option<&'a mut FunctionType<'a>>, // FunctionTypePtr + pub callback_target: Option<&'a mut Function<'a>>, // FunctionPtr + pub callback_exceptional_return: Option<&'a mut Instance<'a>>, // InstancePtr + pub ffi_function_kind: u8, + pub callback_id: i32, +} + +pub struct Field<'a> { + pub name: std::string::String, // StringPtr -> Raw String + pub owner: Option<&'a mut Object<'a>>, // ObjectPtr + pub type_field: Option<&'a mut AbstractType<'a>>, // AbstractTypePtr + pub initializer_function: Option<&'a mut Function<'a>>, // FunctionPtr + pub host_offset_or_field_id: Smi, // Smi + pub guarded_list_length: Smi, // Smi + pub exact_type: Option<&'a mut AbstractType<'a>>, // AbstractTypePtr + pub dependent_code: Option<&'a mut WeakArray<'a>>, // WeakArrayPtr + pub kernel_offset: i32, + pub guarded_list_length_in_object_offset: i8, + pub static_type_exactness_state: i8, + pub target_offset: i32, + pub kind_bits: u32, +} + +pub struct Namespace<'a> { + pub target: Option<&'a mut Library<'a>>, // LibraryPtr + pub show_names: Option<&'a mut Array<'a>>, // ArrayPtr + pub hide_names: Option<&'a mut Array<'a>>, // ArrayPtr + pub owner: Option<&'a mut Library<'a>>, // LibraryPtr +} + +pub struct KernelProgramInfo<'a> { + pub kernel_component: Option<&'a mut TypedDataBase<'a>>, // TypedDataBasePtr + pub string_offsets: Option<&'a mut TypedData<'a>>, // TypedDataPtr + pub string_data: Option<&'a mut TypedDataView<'a>>, // TypedDataViewPtr + pub canonical_names: Option<&'a mut TypedData<'a>>, // TypedDataPtr + pub metadata_payloads: Option<&'a mut TypedDataView<'a>>, // TypedDataViewPtr + pub metadata_mappings: Option<&'a mut TypedDataView<'a>>, // TypedDataViewPtr + pub scripts: Option<&'a mut Array<'a>>, // ArrayPtr + pub constants: Option<&'a mut Array<'a>>, // ArrayPtr + pub constants_table: Option<&'a mut TypedDataView<'a>>, // TypedDataViewPtr + pub libraries_cache: Option<&'a mut Array<'a>>, // ArrayPtr + pub classes_cache: Option<&'a mut Array<'a>>, // ArrayPtr +} + +pub struct ExceptionHandlers<'a> { + pub handled_types_data: Option<&'a mut Array<'a>>, // ArrayPtr + pub packed_fields: u32, +} + +pub struct Context<'a> { + pub parent: Option<&'a mut Context<'a>>, // ContextPtr + pub num_variables: i32, +} + +pub struct UnlinkedCall { + pub can_patch_to_monomorphic: bool, +} + +pub struct String { + pub hash: Smi, // Smi + pub length: Smi, // Smi +} + +pub struct Class<'a> { + pub name: std::string::String, // StringPtr -> Raw String + pub user_name: std::string::String, // StringPtr -> Raw String + pub functions: Option<&'a mut Array<'a>>, // ArrayPtr + pub functions_hash_table: Option<&'a mut Array<'a>>, // ArrayPtr + pub fields: Option<&'a mut Array<'a>>, // ArrayPtr + pub offset_in_words_to_field: Option<&'a mut Array<'a>>, // ArrayPtr + pub interfaces: Option<&'a mut Array<'a>>, // ArrayPtr + pub script: Option<&'a mut Script<'a>>, // ScriptPtr + pub library: Option<&'a mut Library<'a>>, // LibraryPtr + pub type_parameters: Option<&'a mut TypeParameters<'a>>, // TypeParametersPtr + pub super_type: Option<&'a mut Type<'a>>, // TypePtr + pub constants: Option<&'a mut Array<'a>>, // ArrayPtr + pub declaration_type: Option<&'a mut Type<'a>>, // TypePtr + pub invocation_dispatcher_cache: Option<&'a mut Array<'a>>, // ArrayPtr + pub direct_implementors: Option<&'a mut GrowableObjectArray<'a>>, // GrowableObjectArrayPtr + pub direct_subclasses: Option<&'a mut GrowableObjectArray<'a>>, // GrowableObjectArrayPtr + pub declaration_instance_type_arguments: Option<&'a mut TypeArguments<'a>>, // TypeArgumentsPtr + pub allocation_stub: Option<&'a mut Code<'a>>, // CodePtr + pub dependent_code: Option<&'a mut WeakArray<'a>>, // WeakArrayPtr + pub num_type_arguments: i16, + pub num_native_fields: u16, + pub state_bits: u32, + pub host_instance_size_in_words: i32, + pub host_type_arguments_field_offset_in_words: i32, + pub host_next_field_offset_in_words: i32, + pub target_instance_size_in_words: i32, + pub target_type_arguments_field_offset_in_words: i32, + pub target_next_field_offset_in_words: i32, + pub kernel_offset: i32, +} + +pub struct Function<'a> { + pub name: std::string::String, // StringPtr -> Raw String + pub owner: Option<&'a mut Object<'a>>, // ObjectPtr + pub signature: Option<&'a mut FunctionType<'a>>, // FunctionTypePtr + pub data: Option<&'a mut Object<'a>>, // ObjectPtr + pub ic_data_array_or_bytecode: Option<&'a mut Object<'a>>, // ObjectPtr + pub code: Option<&'a mut Code<'a>>, // CodePtr + pub positional_parameter_names: Option<&'a mut Array<'a>>, // ArrayPtr + pub unoptimized_code: Option<&'a mut Code<'a>>, // CodePtr + pub bitmap: u64, + pub kernel_offset: i32, + pub kind_tag: u32, +} + +pub struct Library<'a> { + pub name: std::string::String, // StringPtr -> Raw String + pub url: std::string::String, // StringPtr -> Raw String + pub private_key: std::string::String, // StringPtr -> Raw String + pub dictionary: Option<&'a mut Array<'a>>, // ArrayPtr + pub metadata: Option<&'a mut Array<'a>>, // ArrayPtr + pub toplevel_class: Option<&'a mut Class<'a>>, // ClassPtr + pub used_scripts: Option<&'a mut GrowableObjectArray<'a>>, // GrowableObjectArrayPtr + pub loading_unit: Option<&'a mut LoadingUnit<'a>>, // LoadingUnitPtr + pub imports: Option<&'a mut Array<'a>>, // ArrayPtr + pub exports: Option<&'a mut Array<'a>>, // ArrayPtr + pub dependencies: Option<&'a mut Array<'a>>, // ArrayPtr + pub kernel_program_info: Option<&'a mut KernelProgramInfo<'a>>, // KernelProgramInfoPtr + pub loaded_scripts: Option<&'a mut Array<'a>>, // ArrayPtr + pub num_imports: u16, + pub load_state: i8, + pub flags: u8, + pub kernel_library_index: i32, +} + +pub struct ContextScope { + pub num_variables: i32, + pub is_implicit: bool, +} + +// Fieldless classes +// These classes either have no additional payload fields beyond the standard +// instance headers or their payloads are purely variable-length or dynamically read/overlayed on top of a byte stream + +pub struct CodeSourceMap; +pub struct CompressedStackMaps; +pub struct PcDescriptors; +pub struct ObjectPool; +pub struct OneByteString; +pub struct TwoByteString; + +// Placeholder structs for references used above that aren't defined yet +// it wouldn't compile without this, though for now they remain unimplemented... +pub struct Array<'a> { _marker: std::marker::PhantomData<&'a ()> } +pub struct Object<'a> { _marker: std::marker::PhantomData<&'a ()> } +pub struct AbstractType<'a> { _marker: std::marker::PhantomData<&'a ()> } +pub struct FunctionType<'a> { _marker: std::marker::PhantomData<&'a ()> } +pub struct Script<'a> { _marker: std::marker::PhantomData<&'a ()> } +pub struct Closure<'a> { _marker: std::marker::PhantomData<&'a ()> } +pub struct Instance<'a> { _marker: std::marker::PhantomData<&'a ()> } +pub struct WeakArray<'a> { _marker: std::marker::PhantomData<&'a ()> } +pub struct TypedDataBase<'a> { _marker: std::marker::PhantomData<&'a ()> } +pub struct TypedData<'a> { _marker: std::marker::PhantomData<&'a ()> } +pub struct TypedDataView<'a> { _marker: std::marker::PhantomData<&'a ()> } +pub struct GrowableObjectArray<'a> { _marker: std::marker::PhantomData<&'a ()> } +pub struct Code<'a> { _marker: std::marker::PhantomData<&'a ()> } +pub struct LoadingUnit<'a> { _marker: std::marker::PhantomData<&'a ()> } diff --git a/crates/flutterdec-serwalker/src/snapshot.rs b/crates/flutterdec-serwalker/src/snapshot.rs new file mode 100644 index 0000000..f109274 --- /dev/null +++ b/crates/flutterdec-serwalker/src/snapshot.rs @@ -0,0 +1,84 @@ +use crate::cluster::{ Cluster }; +use crate::stream::{ Stream }; +use crate::constants::{self, MAGIC_BYTES}; + +enum SnapshotKind // pulled straight out of the C++ def +{ + Full, + FullCore, + FullJIT, + FullAOT, // Full + AOT code, this is the one we care about, as th + Module, + None, + Invalid +} + +impl TryFrom for SnapshotKind { + type Error = &'static str; + + fn try_from(value: u64) -> Result { + match value { + 0 => Ok(SnapshotKind::Full), + 1 => Ok(SnapshotKind::FullCore), + 2 => Ok(SnapshotKind::FullJIT), + 3 => Ok(SnapshotKind::FullAOT), + 4 => Ok(SnapshotKind::Module), + 5 => Ok(SnapshotKind::None), + 6 => Ok(SnapshotKind::Invalid), + _ => Err("Invalid snapshot kind... Either headers are corrupt, or this is not a snapshot at all."), // Handle invalid snapshot kidjns + } + } +} +struct DataSnapshot +{ + // this array will contain mutable references to all clusters, and it will be indexed using the class id + clusters: [Box; constants::MAX_CLUSTER_NUM], // thus each cluster must have its own UNIQUE class id + + magic_bytes: u32, + size: u64, + kind: SnapshotKind, + + version_hash: String, + features: String, + + num_base_objects: u64, + num_objects: u64, + num_clusters: u64, + + instr_table_len: u64, + instr_table_offset: usize, + + start_of_alloc_area: usize, + start_of_fill_area: usize, +} + +impl DataSnapshot { + fn parse_header(&mut self, stream: &mut Stream) + { + self.magic_bytes = stream.read_u32(); + + if self.magic_bytes != MAGIC_BYTES + { + panic!("Not a snapshot...") + } + + self.size = stream.read_u64(); + self.kind = SnapshotKind::try_from(stream.read_u64()).expect("Not a valid snapshot!"); + + self.parse_version_and_features(stream); + } + + fn parse_version_and_features(&mut self, stream: &mut Stream) + { + let mut version_and_features = stream.read_c_string(); + + self.features = version_and_features.split_off(constants::VERSION_HASH_LENGTH); // returns (str[hash_len..]) + self.version_hash = version_and_features; + } + + fn read_clusters(&mut self, stream: &Stream) + { + let curr_ref_id: u64 = 0; // all objects are numbered starting from 0 + + } +} \ No newline at end of file diff --git a/crates/flutterdec-serwalker/src/stream.rs b/crates/flutterdec-serwalker/src/stream.rs index d38fb0b..2a9c45d 100644 --- a/crates/flutterdec-serwalker/src/stream.rs +++ b/crates/flutterdec-serwalker/src/stream.rs @@ -1,20 +1,111 @@ -mod constants; - -struct Stream +use crate::constants::{UNSIGNED_END_OF_DATA_BYTE, SIGNED_END_OF_DATA_BYTE, UNSIGNED_MAX_DATA_PER_BYTE, DATA_BITS_PER_BYTE}; +pub struct Stream<'a> { - byte_stream: &[u8], + byte_stream: &'a [u8], curr_stream_offset: usize, } -impl Stream +impl<'a> Stream<'a> { - fn seek(&self, pos: usize) + fn seek(&mut self, pos: usize) // might be useful? + { + if self.byte_stream.len() > pos && pos > 0 { self.curr_stream_offset = pos; } + } + + pub fn advance_pos(&mut self, num_bytes: usize) + { + self.curr_stream_offset += num_bytes; + } + + pub fn get_current_pos(&self) -> usize + { + self.curr_stream_offset + } + /* + Reads a modified uleb from the current stream offset. + + Dart uses a modified LEB128 format. The normal format uses bytes with their MSb set in order + to signify that there are more bytes ahead, and the last byte has its MSb unset, whereas Dart's + implementation does the opposite. The "continuation" bit on each byte is 0, and the last byte + has its MSb set. + */ + pub fn read_modified_leb128(&mut self, sign_marker: u8) -> u64 // 8 bytes should be enough for anything... + { + let mut idx: u8 = 0; + + let first_byte = self.byte_stream[self.curr_stream_offset]; + if first_byte > UNSIGNED_MAX_DATA_PER_BYTE // if the first byte has its MSb set + { + self.advance_pos(1); + // wrapping_sub mimics C++ unsigned underflow, giving us perfect sign-extension + // for negative numbers, while behaving normally for positive numbers. gotta get used to this :) + return (first_byte as u64).wrapping_sub(sign_marker as u64); + } + + let mut read_num: u64 = 0; + let mut byte: u8; + + loop { + byte = self.byte_stream[self.curr_stream_offset + idx as usize]; + if byte & UNSIGNED_END_OF_DATA_BYTE == UNSIGNED_END_OF_DATA_BYTE { break; } // final byte + read_num |= (byte as u64) << (idx as usize * DATA_BITS_PER_BYTE); + idx += 1; + } + + self.advance_pos((idx + 1) as usize); // advance the stream position + + // Same wrapping trick for the final byte + let final_chunk = (byte as u64).wrapping_sub(sign_marker as u64); + read_num |= final_chunk << (idx as usize * DATA_BITS_PER_BYTE); + + read_num + } + + pub fn read_u64(&mut self) -> u64 { - self.curr_stream_offset = pos; + let u64_size = std::mem::size_of::(); + let num_slice = &self.byte_stream[self.curr_stream_offset..self.curr_stream_offset + u64_size]; + + let converted_slice: [u8; 8] = num_slice + .try_into() + .expect("Slice wasn't 8 bytes long..."); + + self.advance_pos(u64_size); + + u64::from_le_bytes(converted_slice) } - fn read_modified_uleb128(stream: &[u8]) // read a modified uleb in the current poss + pub fn read_u32(&mut self) -> u32 { - let bytes = Vec + let u32_size = std::mem::size_of::(); + let num_slice = &self.byte_stream[self.curr_stream_offset..self.curr_stream_offset + u32_size]; + + let converted_slice: [u8; 4] = num_slice + .try_into() + .expect("Slice wasn't 4 bytes long..."); + + self.advance_pos(u32_size); + + u32::from_le_bytes(converted_slice) + } + + /* + Panics if it isn't possible to create a stream from the utf-8 representation stored in + the byte slice. It shouldn't happen, so the best possible outcome is to assume some + logic mistake has been made and end the application. It should be a good thing to change this to an + unwrap_or_else so that we can also print the stream offset and cluster where this error occurred, in order + to have static debug info. + */ + pub fn read_c_string(&mut self) -> String + { + let first_nullbyte_pos = self.byte_stream + .iter() + .position(|&b| b == 0x00) + .unwrap_or(self.byte_stream.len()); + + let raw_str = &self.byte_stream[..=first_nullbyte_pos]; + self.advance_pos(raw_str.len()); + + String::from_utf8(raw_str.to_vec()).unwrap() // it should be horrible if for some reason a string just isn't there } } \ No newline at end of file diff --git a/crates/flutterdec-serwalker/src/utils.rs b/crates/flutterdec-serwalker/src/utils.rs index 72bd0fb..049d66f 100644 --- a/crates/flutterdec-serwalker/src/utils.rs +++ b/crates/flutterdec-serwalker/src/utils.rs @@ -1,83 +1,54 @@ +use paste::paste; +use crate::cluster::Cluster; +use crate::stream::Stream; +use crate::constants::UNSIGNED_M; -struct DataSnapshot +#[macro_export] +macro_rules! DECLARE_FIXED_LENGTH_CLUSTER { - clusters: Vec<&'static mut dyn Cluster>, + ($name:ident, $fill_impl:block) => { + ::paste::paste! { // this is ugly, but the language doesn't support identifier concatenation + struct [<$name Cluster>] + { + tags: u32, + obj_count: u64, - magic_bytes: u32, - size: u64, - kind: u64, + start_of_fill: usize, + start_of_alloc: usize, - version: String, - features: String, + end_of_fill: usize, + end_of_alloc: usize, - num_base_objects: u64, - num_objects: u64, - num_clusters: u64, + objs: Vec<(u64, Box<$name>)> // a pair (ref_id, object) + } - instr_table_len: u64, - instr_table_offset: u64, -} -trait Cluster -{ - fn is_fixed_len(&self) -> bool; - fn get_size(&self) -> usize; -} + impl Cluster for [<$name Cluster>] + { + fn read_alloc(&mut self, last_ref_id: &mut u64, stream: &mut Stream) -> usize // read tags and count + { + let initial_pos = stream.get_current_pos(); + self.start_of_alloc = initial_pos; -macro_rules! DECLARE_FIXED_LENGTH_CLUSTER -{ - ($name:ident, $instance_size:literal) => { - struct $name - { - tags: u32, - count: u64, - } + self.obj_count = stream.read_modified_leb128(UNSIGNED_M); - impl Cluster for $name - { - fn get_size(&self) -> usize - { - $instance_size - } + for obj_idx in 0..self.obj_count + { + self.objs.push((*last_ref_id + obj_idx, Box::<$name>::default())); + } - fn is_fixed_len(&self) -> bool - { - true - } - } - }; -} + *last_ref_id = *last_ref_id + self.obj_count; -macro_rules! DECLARE_VARIABLE_LENGTH_CLUSTER -{ - ($name:ident, $get_length_impl:block) => { - struct $name - { - tags: u32, - count: u64 - } + stream.get_current_pos() - initial_pos + } - impl Cluster for $name - { - fn get_size(&self) -> usize - $get_length_impl + fn read_fill(&mut self, last_ref_id: &mut u64, stream: &mut Stream) -> usize + $fill_impl - fn is_fixed_len(&self) -> bool - { - false + fn is_fixed_len(&self) -> bool + { + true + } } } }; -} - -// These are the objects that call ReadAllocFixedSize during deserialization, -// whose fill cluster size is uniquely determined by sizeof(Object) * num_of_objects -// and alloc cluster size is tags (u32) + num_of_objects (ULEB128) - -DECLARE_FIXED_LENGTH_CLUSTER!(OneByteStringCluster, 2); -DECLARE_FIXED_LENGTH_CLUSTER!(TwoByteStringCluster, 8); -DECLARE_FIXED_LENGTH_CLUSTER!(StringCluster, 8); -DECLARE_FIXED_LENGTH_CLUSTER!(MintCluster, 16); -DECLARE_FIXED_LENGTH_CLUSTER!(DoubleCluster, 16); -DECLARE_FIXED_LENGTH_CLUSTER!(TypeParameterCluster, 32); -DECLARE_FIXED_LENGTH_CLUSTER!(TypeCluster, 32); -DECLARE_FIXED_LENGTH_CLUSTER!(TypeArgumentsCluster, 32); +} \ No newline at end of file From 9d9eaaec1d0c6c09698afb674ebc95936db491c3 Mon Sep 17 00:00:00 2001 From: kalixtez Date: Mon, 27 Apr 2026 14:24:37 -0500 Subject: [PATCH 08/50] feat(serwalker): add stream readers, fixed-alloc base struct, ClassId enum --- Cargo.lock | 7 + crates/flutterdec-serwalker/Cargo.toml | 3 +- .../flutterdec-serwalker/src/cluster/mod.rs | 56 +++++ crates/flutterdec-serwalker/src/constants.rs | 207 ++++++++++++++++- crates/flutterdec-serwalker/src/lib.rs | 5 + .../src/raw_object/mod.rs | 214 ++++++++++++++++++ crates/flutterdec-serwalker/src/snapshot.rs | 84 +++++++ crates/flutterdec-serwalker/src/stream.rs | 109 ++++++++- crates/flutterdec-serwalker/src/utils.rs | 107 ++++----- 9 files changed, 710 insertions(+), 82 deletions(-) create mode 100644 crates/flutterdec-serwalker/src/cluster/mod.rs create mode 100644 crates/flutterdec-serwalker/src/raw_object/mod.rs create mode 100644 crates/flutterdec-serwalker/src/snapshot.rs diff --git a/Cargo.lock b/Cargo.lock index c413b1d..6482e4e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -366,6 +366,7 @@ name = "flutterdec-serwalker" version = "0.1.0-alpha.2" dependencies = [ "goblin", + "paste", ] [[package]] @@ -571,6 +572,12 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + [[package]] name = "plain" version = "0.2.3" diff --git a/crates/flutterdec-serwalker/Cargo.toml b/crates/flutterdec-serwalker/Cargo.toml index 65fb1eb..1885862 100644 --- a/crates/flutterdec-serwalker/Cargo.toml +++ b/crates/flutterdec-serwalker/Cargo.toml @@ -5,4 +5,5 @@ edition.workspace = true license.workspace = true [dependencies] -goblin.workspace = true \ No newline at end of file +goblin.workspace = true +paste = "1.0.15" diff --git a/crates/flutterdec-serwalker/src/cluster/mod.rs b/crates/flutterdec-serwalker/src/cluster/mod.rs new file mode 100644 index 0000000..568bdb5 --- /dev/null +++ b/crates/flutterdec-serwalker/src/cluster/mod.rs @@ -0,0 +1,56 @@ +use crate::{constants, stream::Stream}; +use crate::DECLARE_FIXED_LENGTH_CLUSTER; +use crate::raw_object::*; +use crate::constants::{ClassId, ClassId::*, SIGNED_M, UNSIGNED_M}; + +type Smi = i32; + +pub trait Cluster +{ + fn is_fixed_len(&self) -> bool; + fn read_alloc(&mut self, last_ref_id: &mut u64, stream: &mut Stream) -> usize; + fn read_fill(&mut self, last_ref_id: &mut u64, stream: &mut Stream) -> usize; +} + +pub fn read_cluster_alloc() +{ + let curr_ref_id: u64 = 0; +} + +pub fn read_cluster_fill() +{ + let curr_ref_id: u64 = 0; +} + +pub fn read_and_decompress_smi(stream: &mut Stream) -> Smi +{ + let raw_smi = stream.read_modified_leb128(UNSIGNED_M); // smis are always written as signed + + (raw_smi as Smi) >> constants::SMI_SHIFT +} + +pub fn decide_cluster(clusters: &mut [Box; constants::MAX_CLUSTER_NUM], class_id: ClassId) -> Result, &str> +{ + match class_id { + IllegalCid => Err("Not a supported class (illegal class)..."), + _ => Err("Not a supported class..."), + } +} + +// These are the objects that call ReadAllocFixedSize during deserialization, +// whose fill cluster size is uniquely determined by sizeof(Object) * num_of_objects +// and alloc cluster size is tags (MULEB128) + num_of_objects (MULEB128) + + +DECLARE_FIXED_LENGTH_CLUSTER!(OneByteString, { 1 + // to-do +}); +/* +DECLARE_FIXED_LENGTH_CLUSTER!(TwoByteString, 8); +DECLARE_FIXED_LENGTH_CLUSTER!(String, 8); +DECLARE_FIXED_LENGTH_CLUSTER!(Mint, 16); +DECLARE_FIXED_LENGTH_CLUSTER!(Double, 16); +DECLARE_FIXED_LENGTH_CLUSTER!(TypeParameter, 32); +DECLARE_FIXED_LENGTH_CLUSTER!(Type, 32); +DECLARE_FIXED_LENGTH_CLUSTER!(TypeArguments, 32); +*/ \ No newline at end of file diff --git a/crates/flutterdec-serwalker/src/constants.rs b/crates/flutterdec-serwalker/src/constants.rs index b896046..63b35c6 100644 --- a/crates/flutterdec-serwalker/src/constants.rs +++ b/crates/flutterdec-serwalker/src/constants.rs @@ -1,13 +1,212 @@ use std::mem::size_of; +pub const MAGIC_BYTES: u32 = 0xdcdcf5f5; + pub const SNAPSHOT_MAGIC_NUMBER_SZ: usize = size_of::(); +pub const SNAPSHOT_LEN_SZ: usize = size_of::(); +pub const SNAPSHOT_KIND_SZ: usize = size_of::(); + +pub const SNAPSHOT_HEADER_SZ: usize = SNAPSHOT_MAGIC_NUMBER_SZ // 20 bytes of header + + SNAPSHOT_LEN_SZ + + SNAPSHOT_KIND_SZ; + +pub const MAX_CLUSTER_NUM: usize = 67usize; + +pub const UNSIGNED_END_OF_DATA_BYTE: u8 = 0x80u8; // last byte +pub const UNSIGNED_MAX_DATA_PER_BYTE: u8 = 0x7fu8; // more bytes to follow (for both) + +pub const SIGNED_END_OF_DATA_BYTE: u8 = 0xc0u8; // last byte -pub const END_OF_ULEB_MASK: u8 = 0x80; // last byte -pub const ULEB_EXTRACT_BYTE_DATA_MASK: u8 = 0x7f; // more bytes to follow +pub const SIGNED_M: u8 = SIGNED_END_OF_DATA_BYTE; +pub const UNSIGNED_M: u8 = UNSIGNED_END_OF_DATA_BYTE; + +pub const DATA_BITS_PER_BYTE: usize = 7usize; + +pub const SMI_SHIFT: usize = 1usize; + +pub const VERSION_HASH_LENGTH: usize = 32usize; + +pub enum ClassId { + IllegalCid = 0, + NativePointer, + FreeListElement, + ForwardingCorpse, + ObjectCid, + ClassCid, + PatchClassCid, + FunctionCid, + TypeParametersCid, + ClosureDataCid, + FfiTrampolineDataCid, + FieldCid, + ScriptCid, + LibraryCid, + NamespaceCid, + KernelProgramInfoCid, + WeakSerializationReferenceCid, + WeakArrayCid, + CodeCid, + BytecodeCid, + InstructionsCid, + InstructionsSectionCid, + InstructionsTableCid, + ObjectPoolCid, + PcDescriptorsCid, + CodeSourceMapCid, + CompressedStackMapsCid, + LocalVarDescriptorsCid, + ExceptionHandlersCid, + ContextCid, + ContextScopeCid, + SentinelCid, + SingleTargetCacheCid, + MonomorphicSmiableCallCid, + CallSiteDataCid, + UnlinkedCallCid, + ICDataCid, + MegamorphicCacheCid, + SubtypeTestCacheCid, + LoadingUnitCid, + ErrorCid, + ApiErrorCid, + LanguageErrorCid, + UnhandledExceptionCid, + UnwindErrorCid, + InstanceCid, + LibraryPrefixCid, + TypeArgumentsCid, + AbstractTypeCid, + TypeCid, + FunctionTypeCid, + RecordTypeCid, + TypeParameterCid, + FinalizerBaseCid, + FinalizerCid, + NativeFinalizerCid, + FinalizerEntryCid, + ClosureCid, + NumberCid, + IntegerCid, + SmiCid, + MintCid, + DoubleCid, + BoolCid, + Float32x4Cid, + Int32x4Cid, + Float64x2Cid, + RecordCid, + TypedDataBaseCid, + TypedDataCid, + ExternalTypedDataCid, + TypedDataViewCid, + PointerCid, + DynamicLibraryCid, + CapabilityCid, + ReceivePortCid, + SendPortCid, + StackTraceCid, + SuspendStateCid, + RegExpCid, + WeakPropertyCid, + WeakReferenceCid, + MirrorReferenceCid, + FutureOrCid, + UserTagCid, + TransferableTypedDataCid, + MapCid, + ConstMapCid, + SetCid, + ConstSetCid, + ArrayCid, + ImmutableArrayCid, + GrowableObjectArrayCid, + StringCid, + OneByteStringCid, + TwoByteStringCid, + FfiNativeFunctionCid, + FfiInt8Cid, + FfiInt16Cid, + FfiInt32Cid, + FfiInt64Cid, + FfiUint8Cid, + FfiUint16Cid, + FfiUint32Cid, + FfiUint64Cid, + FfiFloatCid, + FfiDoubleCid, + FfiVoidCid, + FfiHandleCid, + FfiBoolCid, + FfiNativeTypeCid, + FfiStructCid, + TypedDataInt8ArrayCid, + TypedDataInt8ArrayViewCid, + ExternalTypedDataInt8ArrayCid, + UnmodifiableTypedDataInt8ArrayViewCid, + TypedDataUint8ArrayCid, + TypedDataUint8ArrayViewCid, + ExternalTypedDataUint8ArrayCid, + UnmodifiableTypedDataUint8ArrayViewCid, + TypedDataUint8ClampedArrayCid, + TypedDataUint8ClampedArrayViewCid, + ExternalTypedDataUint8ClampedArrayCid, + UnmodifiableTypedDataUint8ClampedArrayViewCid, + TypedDataInt16ArrayCid, + TypedDataInt16ArrayViewCid, + ExternalTypedDataInt16ArrayCid, + UnmodifiableTypedDataInt16ArrayViewCid, + TypedDataUint16ArrayCid, + TypedDataUint16ArrayViewCid, + ExternalTypedDataUint16ArrayCid, + UnmodifiableTypedDataUint16ArrayViewCid, + TypedDataInt32ArrayCid, + TypedDataInt32ArrayViewCid, + ExternalTypedDataInt32ArrayCid, + UnmodifiableTypedDataInt32ArrayViewCid, + TypedDataUint32ArrayCid, + TypedDataUint32ArrayViewCid, + ExternalTypedDataUint32ArrayCid, + UnmodifiableTypedDataUint32ArrayViewCid, + TypedDataInt64ArrayCid, + TypedDataInt64ArrayViewCid, + ExternalTypedDataInt64ArrayCid, + UnmodifiableTypedDataInt64ArrayViewCid, + TypedDataUint64ArrayCid, + TypedDataUint64ArrayViewCid, + ExternalTypedDataUint64ArrayCid, + UnmodifiableTypedDataUint64ArrayViewCid, + TypedDataFloat32ArrayCid, + TypedDataFloat32ArrayViewCid, + ExternalTypedDataFloat32ArrayCid, + UnmodifiableTypedDataFloat32ArrayViewCid, + TypedDataFloat64ArrayCid, + TypedDataFloat64ArrayViewCid, + ExternalTypedDataFloat64ArrayCid, + UnmodifiableTypedDataFloat64ArrayViewCid, + TypedDataFloat32x4ArrayCid, + TypedDataFloat32x4ArrayViewCid, + ExternalTypedDataFloat32x4ArrayCid, + UnmodifiableTypedDataFloat32x4ArrayViewCid, + TypedDataInt32x4ArrayCid, + TypedDataInt32x4ArrayViewCid, + ExternalTypedDataInt32x4ArrayCid, + UnmodifiableTypedDataInt32x4ArrayViewCid, + TypedDataFloat64x2ArrayCid, + TypedDataFloat64x2ArrayViewCid, + ExternalTypedDataFloat64x2ArrayCid, + UnmodifiableTypedDataFloat64x2ArrayViewCid, + ByteDataViewCid, + UnmodifiableByteDataViewCid, + ByteBufferCid, + NullCid, + DynamicCid, + VoidCid, + NeverCid, + NumPredefinedCids, +} /* -pub const SNAPSHOT_LEN_SZ: usize = size_of::(); -pub const SNAPSHOT_KIND_SZ: usize = size_of::(); + pub const NUM_BASE_OBJECTS_SZ: usize = size_of::(); pub const NUM_OBJECTS_SZ: usize = size_of::(); diff --git a/crates/flutterdec-serwalker/src/lib.rs b/crates/flutterdec-serwalker/src/lib.rs index 22884e4..1f91630 100644 --- a/crates/flutterdec-serwalker/src/lib.rs +++ b/crates/flutterdec-serwalker/src/lib.rs @@ -1,3 +1,8 @@ mod utils; mod constants; +mod cluster; +mod raw_object; +mod stream; + +mod snapshot; \ No newline at end of file diff --git a/crates/flutterdec-serwalker/src/raw_object/mod.rs b/crates/flutterdec-serwalker/src/raw_object/mod.rs new file mode 100644 index 0000000..3ac4da2 --- /dev/null +++ b/crates/flutterdec-serwalker/src/raw_object/mod.rs @@ -0,0 +1,214 @@ +// raw_object/mod.rs + +type Smi = i64; // Using i64 for Smi fields to be decompressed + +// --- Fixed-Size Objects with defined fields --- + +pub struct Mint { + pub value: i64, +} + +pub struct Double { + pub value: f64, +} + +pub struct TypeArguments<'a> { + pub instantiations: Option<&'a mut Array<'a>>, // ArrayPtr + pub length: Smi, // Smi + pub hash: Smi, // Smi + pub nullability: Smi, // Smi +} + +pub struct TypeParameter<'a> { + pub owner: Option<&'a mut Object<'a>>, // ObjectPtr + pub base: i16, + pub index: i16, +} + +pub struct Type<'a> { + pub arguments: Option<&'a mut TypeArguments<'a>>, // TypeArgumentsPtr +} + +pub struct TypeParameters<'a> { + pub names: Option<&'a mut Array<'a>>, // ArrayPtr + pub flags: Option<&'a mut Array<'a>>, // ArrayPtr + pub bounds: Option<&'a mut TypeArguments<'a>>, // TypeArgumentsPtr + pub defaults: Option<&'a mut TypeArguments<'a>>, // TypeArgumentsPtr +} + +pub struct PatchClass<'a> { + pub wrapped_class: Option<&'a mut Class<'a>>, // ClassPtr + pub script: Option<&'a mut Script<'a>>, // ScriptPtr + pub kernel_program_info: Option<&'a mut KernelProgramInfo<'a>>, // KernelProgramInfoPtr +} + +pub struct ClosureData<'a> { + pub context_scope: Option<&'a mut ContextScope>, // ContextScopePtr + pub parent_function: Option<&'a mut Function<'a>>, // FunctionPtr + pub closure: Option<&'a mut Closure<'a>>, // ClosurePtr + pub packed_fields: u32, +} + +pub struct FfiTrampolineData<'a> { + pub signature_type: Option<&'a mut Type<'a>>, // TypePtr + pub c_signature: Option<&'a mut FunctionType<'a>>, // FunctionTypePtr + pub callback_target: Option<&'a mut Function<'a>>, // FunctionPtr + pub callback_exceptional_return: Option<&'a mut Instance<'a>>, // InstancePtr + pub ffi_function_kind: u8, + pub callback_id: i32, +} + +pub struct Field<'a> { + pub name: std::string::String, // StringPtr -> Raw String + pub owner: Option<&'a mut Object<'a>>, // ObjectPtr + pub type_field: Option<&'a mut AbstractType<'a>>, // AbstractTypePtr + pub initializer_function: Option<&'a mut Function<'a>>, // FunctionPtr + pub host_offset_or_field_id: Smi, // Smi + pub guarded_list_length: Smi, // Smi + pub exact_type: Option<&'a mut AbstractType<'a>>, // AbstractTypePtr + pub dependent_code: Option<&'a mut WeakArray<'a>>, // WeakArrayPtr + pub kernel_offset: i32, + pub guarded_list_length_in_object_offset: i8, + pub static_type_exactness_state: i8, + pub target_offset: i32, + pub kind_bits: u32, +} + +pub struct Namespace<'a> { + pub target: Option<&'a mut Library<'a>>, // LibraryPtr + pub show_names: Option<&'a mut Array<'a>>, // ArrayPtr + pub hide_names: Option<&'a mut Array<'a>>, // ArrayPtr + pub owner: Option<&'a mut Library<'a>>, // LibraryPtr +} + +pub struct KernelProgramInfo<'a> { + pub kernel_component: Option<&'a mut TypedDataBase<'a>>, // TypedDataBasePtr + pub string_offsets: Option<&'a mut TypedData<'a>>, // TypedDataPtr + pub string_data: Option<&'a mut TypedDataView<'a>>, // TypedDataViewPtr + pub canonical_names: Option<&'a mut TypedData<'a>>, // TypedDataPtr + pub metadata_payloads: Option<&'a mut TypedDataView<'a>>, // TypedDataViewPtr + pub metadata_mappings: Option<&'a mut TypedDataView<'a>>, // TypedDataViewPtr + pub scripts: Option<&'a mut Array<'a>>, // ArrayPtr + pub constants: Option<&'a mut Array<'a>>, // ArrayPtr + pub constants_table: Option<&'a mut TypedDataView<'a>>, // TypedDataViewPtr + pub libraries_cache: Option<&'a mut Array<'a>>, // ArrayPtr + pub classes_cache: Option<&'a mut Array<'a>>, // ArrayPtr +} + +pub struct ExceptionHandlers<'a> { + pub handled_types_data: Option<&'a mut Array<'a>>, // ArrayPtr + pub packed_fields: u32, +} + +pub struct Context<'a> { + pub parent: Option<&'a mut Context<'a>>, // ContextPtr + pub num_variables: i32, +} + +pub struct UnlinkedCall { + pub can_patch_to_monomorphic: bool, +} + +pub struct String { + pub hash: Smi, // Smi + pub length: Smi, // Smi +} + +pub struct Class<'a> { + pub name: std::string::String, // StringPtr -> Raw String + pub user_name: std::string::String, // StringPtr -> Raw String + pub functions: Option<&'a mut Array<'a>>, // ArrayPtr + pub functions_hash_table: Option<&'a mut Array<'a>>, // ArrayPtr + pub fields: Option<&'a mut Array<'a>>, // ArrayPtr + pub offset_in_words_to_field: Option<&'a mut Array<'a>>, // ArrayPtr + pub interfaces: Option<&'a mut Array<'a>>, // ArrayPtr + pub script: Option<&'a mut Script<'a>>, // ScriptPtr + pub library: Option<&'a mut Library<'a>>, // LibraryPtr + pub type_parameters: Option<&'a mut TypeParameters<'a>>, // TypeParametersPtr + pub super_type: Option<&'a mut Type<'a>>, // TypePtr + pub constants: Option<&'a mut Array<'a>>, // ArrayPtr + pub declaration_type: Option<&'a mut Type<'a>>, // TypePtr + pub invocation_dispatcher_cache: Option<&'a mut Array<'a>>, // ArrayPtr + pub direct_implementors: Option<&'a mut GrowableObjectArray<'a>>, // GrowableObjectArrayPtr + pub direct_subclasses: Option<&'a mut GrowableObjectArray<'a>>, // GrowableObjectArrayPtr + pub declaration_instance_type_arguments: Option<&'a mut TypeArguments<'a>>, // TypeArgumentsPtr + pub allocation_stub: Option<&'a mut Code<'a>>, // CodePtr + pub dependent_code: Option<&'a mut WeakArray<'a>>, // WeakArrayPtr + pub num_type_arguments: i16, + pub num_native_fields: u16, + pub state_bits: u32, + pub host_instance_size_in_words: i32, + pub host_type_arguments_field_offset_in_words: i32, + pub host_next_field_offset_in_words: i32, + pub target_instance_size_in_words: i32, + pub target_type_arguments_field_offset_in_words: i32, + pub target_next_field_offset_in_words: i32, + pub kernel_offset: i32, +} + +pub struct Function<'a> { + pub name: std::string::String, // StringPtr -> Raw String + pub owner: Option<&'a mut Object<'a>>, // ObjectPtr + pub signature: Option<&'a mut FunctionType<'a>>, // FunctionTypePtr + pub data: Option<&'a mut Object<'a>>, // ObjectPtr + pub ic_data_array_or_bytecode: Option<&'a mut Object<'a>>, // ObjectPtr + pub code: Option<&'a mut Code<'a>>, // CodePtr + pub positional_parameter_names: Option<&'a mut Array<'a>>, // ArrayPtr + pub unoptimized_code: Option<&'a mut Code<'a>>, // CodePtr + pub bitmap: u64, + pub kernel_offset: i32, + pub kind_tag: u32, +} + +pub struct Library<'a> { + pub name: std::string::String, // StringPtr -> Raw String + pub url: std::string::String, // StringPtr -> Raw String + pub private_key: std::string::String, // StringPtr -> Raw String + pub dictionary: Option<&'a mut Array<'a>>, // ArrayPtr + pub metadata: Option<&'a mut Array<'a>>, // ArrayPtr + pub toplevel_class: Option<&'a mut Class<'a>>, // ClassPtr + pub used_scripts: Option<&'a mut GrowableObjectArray<'a>>, // GrowableObjectArrayPtr + pub loading_unit: Option<&'a mut LoadingUnit<'a>>, // LoadingUnitPtr + pub imports: Option<&'a mut Array<'a>>, // ArrayPtr + pub exports: Option<&'a mut Array<'a>>, // ArrayPtr + pub dependencies: Option<&'a mut Array<'a>>, // ArrayPtr + pub kernel_program_info: Option<&'a mut KernelProgramInfo<'a>>, // KernelProgramInfoPtr + pub loaded_scripts: Option<&'a mut Array<'a>>, // ArrayPtr + pub num_imports: u16, + pub load_state: i8, + pub flags: u8, + pub kernel_library_index: i32, +} + +pub struct ContextScope { + pub num_variables: i32, + pub is_implicit: bool, +} + +// Fieldless classes +// These classes either have no additional payload fields beyond the standard +// instance headers or their payloads are purely variable-length or dynamically read/overlayed on top of a byte stream + +pub struct CodeSourceMap; +pub struct CompressedStackMaps; +pub struct PcDescriptors; +pub struct ObjectPool; +pub struct OneByteString; +pub struct TwoByteString; + +// Placeholder structs for references used above that aren't defined yet +// it wouldn't compile without this, though for now they remain unimplemented... +pub struct Array<'a> { _marker: std::marker::PhantomData<&'a ()> } +pub struct Object<'a> { _marker: std::marker::PhantomData<&'a ()> } +pub struct AbstractType<'a> { _marker: std::marker::PhantomData<&'a ()> } +pub struct FunctionType<'a> { _marker: std::marker::PhantomData<&'a ()> } +pub struct Script<'a> { _marker: std::marker::PhantomData<&'a ()> } +pub struct Closure<'a> { _marker: std::marker::PhantomData<&'a ()> } +pub struct Instance<'a> { _marker: std::marker::PhantomData<&'a ()> } +pub struct WeakArray<'a> { _marker: std::marker::PhantomData<&'a ()> } +pub struct TypedDataBase<'a> { _marker: std::marker::PhantomData<&'a ()> } +pub struct TypedData<'a> { _marker: std::marker::PhantomData<&'a ()> } +pub struct TypedDataView<'a> { _marker: std::marker::PhantomData<&'a ()> } +pub struct GrowableObjectArray<'a> { _marker: std::marker::PhantomData<&'a ()> } +pub struct Code<'a> { _marker: std::marker::PhantomData<&'a ()> } +pub struct LoadingUnit<'a> { _marker: std::marker::PhantomData<&'a ()> } diff --git a/crates/flutterdec-serwalker/src/snapshot.rs b/crates/flutterdec-serwalker/src/snapshot.rs new file mode 100644 index 0000000..f109274 --- /dev/null +++ b/crates/flutterdec-serwalker/src/snapshot.rs @@ -0,0 +1,84 @@ +use crate::cluster::{ Cluster }; +use crate::stream::{ Stream }; +use crate::constants::{self, MAGIC_BYTES}; + +enum SnapshotKind // pulled straight out of the C++ def +{ + Full, + FullCore, + FullJIT, + FullAOT, // Full + AOT code, this is the one we care about, as th + Module, + None, + Invalid +} + +impl TryFrom for SnapshotKind { + type Error = &'static str; + + fn try_from(value: u64) -> Result { + match value { + 0 => Ok(SnapshotKind::Full), + 1 => Ok(SnapshotKind::FullCore), + 2 => Ok(SnapshotKind::FullJIT), + 3 => Ok(SnapshotKind::FullAOT), + 4 => Ok(SnapshotKind::Module), + 5 => Ok(SnapshotKind::None), + 6 => Ok(SnapshotKind::Invalid), + _ => Err("Invalid snapshot kind... Either headers are corrupt, or this is not a snapshot at all."), // Handle invalid snapshot kidjns + } + } +} +struct DataSnapshot +{ + // this array will contain mutable references to all clusters, and it will be indexed using the class id + clusters: [Box; constants::MAX_CLUSTER_NUM], // thus each cluster must have its own UNIQUE class id + + magic_bytes: u32, + size: u64, + kind: SnapshotKind, + + version_hash: String, + features: String, + + num_base_objects: u64, + num_objects: u64, + num_clusters: u64, + + instr_table_len: u64, + instr_table_offset: usize, + + start_of_alloc_area: usize, + start_of_fill_area: usize, +} + +impl DataSnapshot { + fn parse_header(&mut self, stream: &mut Stream) + { + self.magic_bytes = stream.read_u32(); + + if self.magic_bytes != MAGIC_BYTES + { + panic!("Not a snapshot...") + } + + self.size = stream.read_u64(); + self.kind = SnapshotKind::try_from(stream.read_u64()).expect("Not a valid snapshot!"); + + self.parse_version_and_features(stream); + } + + fn parse_version_and_features(&mut self, stream: &mut Stream) + { + let mut version_and_features = stream.read_c_string(); + + self.features = version_and_features.split_off(constants::VERSION_HASH_LENGTH); // returns (str[hash_len..]) + self.version_hash = version_and_features; + } + + fn read_clusters(&mut self, stream: &Stream) + { + let curr_ref_id: u64 = 0; // all objects are numbered starting from 0 + + } +} \ No newline at end of file diff --git a/crates/flutterdec-serwalker/src/stream.rs b/crates/flutterdec-serwalker/src/stream.rs index d38fb0b..2a9c45d 100644 --- a/crates/flutterdec-serwalker/src/stream.rs +++ b/crates/flutterdec-serwalker/src/stream.rs @@ -1,20 +1,111 @@ -mod constants; - -struct Stream +use crate::constants::{UNSIGNED_END_OF_DATA_BYTE, SIGNED_END_OF_DATA_BYTE, UNSIGNED_MAX_DATA_PER_BYTE, DATA_BITS_PER_BYTE}; +pub struct Stream<'a> { - byte_stream: &[u8], + byte_stream: &'a [u8], curr_stream_offset: usize, } -impl Stream +impl<'a> Stream<'a> { - fn seek(&self, pos: usize) + fn seek(&mut self, pos: usize) // might be useful? + { + if self.byte_stream.len() > pos && pos > 0 { self.curr_stream_offset = pos; } + } + + pub fn advance_pos(&mut self, num_bytes: usize) + { + self.curr_stream_offset += num_bytes; + } + + pub fn get_current_pos(&self) -> usize + { + self.curr_stream_offset + } + /* + Reads a modified uleb from the current stream offset. + + Dart uses a modified LEB128 format. The normal format uses bytes with their MSb set in order + to signify that there are more bytes ahead, and the last byte has its MSb unset, whereas Dart's + implementation does the opposite. The "continuation" bit on each byte is 0, and the last byte + has its MSb set. + */ + pub fn read_modified_leb128(&mut self, sign_marker: u8) -> u64 // 8 bytes should be enough for anything... + { + let mut idx: u8 = 0; + + let first_byte = self.byte_stream[self.curr_stream_offset]; + if first_byte > UNSIGNED_MAX_DATA_PER_BYTE // if the first byte has its MSb set + { + self.advance_pos(1); + // wrapping_sub mimics C++ unsigned underflow, giving us perfect sign-extension + // for negative numbers, while behaving normally for positive numbers. gotta get used to this :) + return (first_byte as u64).wrapping_sub(sign_marker as u64); + } + + let mut read_num: u64 = 0; + let mut byte: u8; + + loop { + byte = self.byte_stream[self.curr_stream_offset + idx as usize]; + if byte & UNSIGNED_END_OF_DATA_BYTE == UNSIGNED_END_OF_DATA_BYTE { break; } // final byte + read_num |= (byte as u64) << (idx as usize * DATA_BITS_PER_BYTE); + idx += 1; + } + + self.advance_pos((idx + 1) as usize); // advance the stream position + + // Same wrapping trick for the final byte + let final_chunk = (byte as u64).wrapping_sub(sign_marker as u64); + read_num |= final_chunk << (idx as usize * DATA_BITS_PER_BYTE); + + read_num + } + + pub fn read_u64(&mut self) -> u64 { - self.curr_stream_offset = pos; + let u64_size = std::mem::size_of::(); + let num_slice = &self.byte_stream[self.curr_stream_offset..self.curr_stream_offset + u64_size]; + + let converted_slice: [u8; 8] = num_slice + .try_into() + .expect("Slice wasn't 8 bytes long..."); + + self.advance_pos(u64_size); + + u64::from_le_bytes(converted_slice) } - fn read_modified_uleb128(stream: &[u8]) // read a modified uleb in the current poss + pub fn read_u32(&mut self) -> u32 { - let bytes = Vec + let u32_size = std::mem::size_of::(); + let num_slice = &self.byte_stream[self.curr_stream_offset..self.curr_stream_offset + u32_size]; + + let converted_slice: [u8; 4] = num_slice + .try_into() + .expect("Slice wasn't 4 bytes long..."); + + self.advance_pos(u32_size); + + u32::from_le_bytes(converted_slice) + } + + /* + Panics if it isn't possible to create a stream from the utf-8 representation stored in + the byte slice. It shouldn't happen, so the best possible outcome is to assume some + logic mistake has been made and end the application. It should be a good thing to change this to an + unwrap_or_else so that we can also print the stream offset and cluster where this error occurred, in order + to have static debug info. + */ + pub fn read_c_string(&mut self) -> String + { + let first_nullbyte_pos = self.byte_stream + .iter() + .position(|&b| b == 0x00) + .unwrap_or(self.byte_stream.len()); + + let raw_str = &self.byte_stream[..=first_nullbyte_pos]; + self.advance_pos(raw_str.len()); + + String::from_utf8(raw_str.to_vec()).unwrap() // it should be horrible if for some reason a string just isn't there } } \ No newline at end of file diff --git a/crates/flutterdec-serwalker/src/utils.rs b/crates/flutterdec-serwalker/src/utils.rs index 72bd0fb..049d66f 100644 --- a/crates/flutterdec-serwalker/src/utils.rs +++ b/crates/flutterdec-serwalker/src/utils.rs @@ -1,83 +1,54 @@ +use paste::paste; +use crate::cluster::Cluster; +use crate::stream::Stream; +use crate::constants::UNSIGNED_M; -struct DataSnapshot +#[macro_export] +macro_rules! DECLARE_FIXED_LENGTH_CLUSTER { - clusters: Vec<&'static mut dyn Cluster>, + ($name:ident, $fill_impl:block) => { + ::paste::paste! { // this is ugly, but the language doesn't support identifier concatenation + struct [<$name Cluster>] + { + tags: u32, + obj_count: u64, - magic_bytes: u32, - size: u64, - kind: u64, + start_of_fill: usize, + start_of_alloc: usize, - version: String, - features: String, + end_of_fill: usize, + end_of_alloc: usize, - num_base_objects: u64, - num_objects: u64, - num_clusters: u64, + objs: Vec<(u64, Box<$name>)> // a pair (ref_id, object) + } - instr_table_len: u64, - instr_table_offset: u64, -} -trait Cluster -{ - fn is_fixed_len(&self) -> bool; - fn get_size(&self) -> usize; -} + impl Cluster for [<$name Cluster>] + { + fn read_alloc(&mut self, last_ref_id: &mut u64, stream: &mut Stream) -> usize // read tags and count + { + let initial_pos = stream.get_current_pos(); + self.start_of_alloc = initial_pos; -macro_rules! DECLARE_FIXED_LENGTH_CLUSTER -{ - ($name:ident, $instance_size:literal) => { - struct $name - { - tags: u32, - count: u64, - } + self.obj_count = stream.read_modified_leb128(UNSIGNED_M); - impl Cluster for $name - { - fn get_size(&self) -> usize - { - $instance_size - } + for obj_idx in 0..self.obj_count + { + self.objs.push((*last_ref_id + obj_idx, Box::<$name>::default())); + } - fn is_fixed_len(&self) -> bool - { - true - } - } - }; -} + *last_ref_id = *last_ref_id + self.obj_count; -macro_rules! DECLARE_VARIABLE_LENGTH_CLUSTER -{ - ($name:ident, $get_length_impl:block) => { - struct $name - { - tags: u32, - count: u64 - } + stream.get_current_pos() - initial_pos + } - impl Cluster for $name - { - fn get_size(&self) -> usize - $get_length_impl + fn read_fill(&mut self, last_ref_id: &mut u64, stream: &mut Stream) -> usize + $fill_impl - fn is_fixed_len(&self) -> bool - { - false + fn is_fixed_len(&self) -> bool + { + true + } } } }; -} - -// These are the objects that call ReadAllocFixedSize during deserialization, -// whose fill cluster size is uniquely determined by sizeof(Object) * num_of_objects -// and alloc cluster size is tags (u32) + num_of_objects (ULEB128) - -DECLARE_FIXED_LENGTH_CLUSTER!(OneByteStringCluster, 2); -DECLARE_FIXED_LENGTH_CLUSTER!(TwoByteStringCluster, 8); -DECLARE_FIXED_LENGTH_CLUSTER!(StringCluster, 8); -DECLARE_FIXED_LENGTH_CLUSTER!(MintCluster, 16); -DECLARE_FIXED_LENGTH_CLUSTER!(DoubleCluster, 16); -DECLARE_FIXED_LENGTH_CLUSTER!(TypeParameterCluster, 32); -DECLARE_FIXED_LENGTH_CLUSTER!(TypeCluster, 32); -DECLARE_FIXED_LENGTH_CLUSTER!(TypeArgumentsCluster, 32); +} \ No newline at end of file From 8de437b14a06505f2436681ff26f35ffd654044b Mon Sep 17 00:00:00 2001 From: kalixtez Date: Mon, 27 Apr 2026 14:29:32 -0500 Subject: [PATCH 09/50] readability change std::string::String -> String --- crates/flutterdec-serwalker/src/raw_object/mod.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/crates/flutterdec-serwalker/src/raw_object/mod.rs b/crates/flutterdec-serwalker/src/raw_object/mod.rs index 3ac4da2..2b7c920 100644 --- a/crates/flutterdec-serwalker/src/raw_object/mod.rs +++ b/crates/flutterdec-serwalker/src/raw_object/mod.rs @@ -59,7 +59,7 @@ pub struct FfiTrampolineData<'a> { } pub struct Field<'a> { - pub name: std::string::String, // StringPtr -> Raw String + pub name: String, // StringPtr -> Raw String pub owner: Option<&'a mut Object<'a>>, // ObjectPtr pub type_field: Option<&'a mut AbstractType<'a>>, // AbstractTypePtr pub initializer_function: Option<&'a mut Function<'a>>, // FunctionPtr @@ -115,8 +115,8 @@ pub struct String { } pub struct Class<'a> { - pub name: std::string::String, // StringPtr -> Raw String - pub user_name: std::string::String, // StringPtr -> Raw String + pub name: String, // StringPtr -> Raw String + pub user_name: String, // StringPtr -> Raw String pub functions: Option<&'a mut Array<'a>>, // ArrayPtr pub functions_hash_table: Option<&'a mut Array<'a>>, // ArrayPtr pub fields: Option<&'a mut Array<'a>>, // ArrayPtr @@ -147,7 +147,7 @@ pub struct Class<'a> { } pub struct Function<'a> { - pub name: std::string::String, // StringPtr -> Raw String + pub name: String, // StringPtr -> Raw String pub owner: Option<&'a mut Object<'a>>, // ObjectPtr pub signature: Option<&'a mut FunctionType<'a>>, // FunctionTypePtr pub data: Option<&'a mut Object<'a>>, // ObjectPtr @@ -161,9 +161,9 @@ pub struct Function<'a> { } pub struct Library<'a> { - pub name: std::string::String, // StringPtr -> Raw String - pub url: std::string::String, // StringPtr -> Raw String - pub private_key: std::string::String, // StringPtr -> Raw String + pub name: String, // StringPtr -> Raw String + pub url: String, // StringPtr -> Raw String + pub private_key: String, // StringPtr -> Raw String pub dictionary: Option<&'a mut Array<'a>>, // ArrayPtr pub metadata: Option<&'a mut Array<'a>>, // ArrayPtr pub toplevel_class: Option<&'a mut Class<'a>>, // ClassPtr From 3991c8119197488c7ebf93cf7d85a9f2c224e495 Mon Sep 17 00:00:00 2001 From: kalixtez Date: Mon, 27 Apr 2026 14:29:32 -0500 Subject: [PATCH 10/50] refactor(serwalker): use String instead of std::string::String --- crates/flutterdec-serwalker/src/raw_object/mod.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/crates/flutterdec-serwalker/src/raw_object/mod.rs b/crates/flutterdec-serwalker/src/raw_object/mod.rs index 3ac4da2..2b7c920 100644 --- a/crates/flutterdec-serwalker/src/raw_object/mod.rs +++ b/crates/flutterdec-serwalker/src/raw_object/mod.rs @@ -59,7 +59,7 @@ pub struct FfiTrampolineData<'a> { } pub struct Field<'a> { - pub name: std::string::String, // StringPtr -> Raw String + pub name: String, // StringPtr -> Raw String pub owner: Option<&'a mut Object<'a>>, // ObjectPtr pub type_field: Option<&'a mut AbstractType<'a>>, // AbstractTypePtr pub initializer_function: Option<&'a mut Function<'a>>, // FunctionPtr @@ -115,8 +115,8 @@ pub struct String { } pub struct Class<'a> { - pub name: std::string::String, // StringPtr -> Raw String - pub user_name: std::string::String, // StringPtr -> Raw String + pub name: String, // StringPtr -> Raw String + pub user_name: String, // StringPtr -> Raw String pub functions: Option<&'a mut Array<'a>>, // ArrayPtr pub functions_hash_table: Option<&'a mut Array<'a>>, // ArrayPtr pub fields: Option<&'a mut Array<'a>>, // ArrayPtr @@ -147,7 +147,7 @@ pub struct Class<'a> { } pub struct Function<'a> { - pub name: std::string::String, // StringPtr -> Raw String + pub name: String, // StringPtr -> Raw String pub owner: Option<&'a mut Object<'a>>, // ObjectPtr pub signature: Option<&'a mut FunctionType<'a>>, // FunctionTypePtr pub data: Option<&'a mut Object<'a>>, // ObjectPtr @@ -161,9 +161,9 @@ pub struct Function<'a> { } pub struct Library<'a> { - pub name: std::string::String, // StringPtr -> Raw String - pub url: std::string::String, // StringPtr -> Raw String - pub private_key: std::string::String, // StringPtr -> Raw String + pub name: String, // StringPtr -> Raw String + pub url: String, // StringPtr -> Raw String + pub private_key: String, // StringPtr -> Raw String pub dictionary: Option<&'a mut Array<'a>>, // ArrayPtr pub metadata: Option<&'a mut Array<'a>>, // ArrayPtr pub toplevel_class: Option<&'a mut Class<'a>>, // ClassPtr From bb1d0932c24eca23edf4f4ac5b2043afdc0e4110 Mon Sep 17 00:00:00 2001 From: kalixtez Date: Mon, 27 Apr 2026 14:38:00 -0500 Subject: [PATCH 11/50] added Default derivation for current classes --- .../src/raw_object/mod.rs | 34 +++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/crates/flutterdec-serwalker/src/raw_object/mod.rs b/crates/flutterdec-serwalker/src/raw_object/mod.rs index 2b7c920..23e5222 100644 --- a/crates/flutterdec-serwalker/src/raw_object/mod.rs +++ b/crates/flutterdec-serwalker/src/raw_object/mod.rs @@ -1,17 +1,19 @@ -// raw_object/mod.rs type Smi = i64; // Using i64 for Smi fields to be decompressed // --- Fixed-Size Objects with defined fields --- +#[derive(Default)] pub struct Mint { pub value: i64, } +#[derive(Default)] pub struct Double { pub value: f64, } +#[derive(Default)] pub struct TypeArguments<'a> { pub instantiations: Option<&'a mut Array<'a>>, // ArrayPtr pub length: Smi, // Smi @@ -19,16 +21,19 @@ pub struct TypeArguments<'a> { pub nullability: Smi, // Smi } +#[derive(Default)] pub struct TypeParameter<'a> { pub owner: Option<&'a mut Object<'a>>, // ObjectPtr pub base: i16, pub index: i16, } +#[derive(Default)] pub struct Type<'a> { pub arguments: Option<&'a mut TypeArguments<'a>>, // TypeArgumentsPtr } +#[derive(Default)] pub struct TypeParameters<'a> { pub names: Option<&'a mut Array<'a>>, // ArrayPtr pub flags: Option<&'a mut Array<'a>>, // ArrayPtr @@ -36,12 +41,14 @@ pub struct TypeParameters<'a> { pub defaults: Option<&'a mut TypeArguments<'a>>, // TypeArgumentsPtr } +#[derive(Default)] pub struct PatchClass<'a> { pub wrapped_class: Option<&'a mut Class<'a>>, // ClassPtr pub script: Option<&'a mut Script<'a>>, // ScriptPtr pub kernel_program_info: Option<&'a mut KernelProgramInfo<'a>>, // KernelProgramInfoPtr } +#[derive(Default)] pub struct ClosureData<'a> { pub context_scope: Option<&'a mut ContextScope>, // ContextScopePtr pub parent_function: Option<&'a mut Function<'a>>, // FunctionPtr @@ -49,6 +56,7 @@ pub struct ClosureData<'a> { pub packed_fields: u32, } +#[derive(Default)] pub struct FfiTrampolineData<'a> { pub signature_type: Option<&'a mut Type<'a>>, // TypePtr pub c_signature: Option<&'a mut FunctionType<'a>>, // FunctionTypePtr @@ -58,6 +66,7 @@ pub struct FfiTrampolineData<'a> { pub callback_id: i32, } +#[derive(Default)] pub struct Field<'a> { pub name: String, // StringPtr -> Raw String pub owner: Option<&'a mut Object<'a>>, // ObjectPtr @@ -74,6 +83,7 @@ pub struct Field<'a> { pub kind_bits: u32, } +#[derive(Default)] pub struct Namespace<'a> { pub target: Option<&'a mut Library<'a>>, // LibraryPtr pub show_names: Option<&'a mut Array<'a>>, // ArrayPtr @@ -81,6 +91,7 @@ pub struct Namespace<'a> { pub owner: Option<&'a mut Library<'a>>, // LibraryPtr } +#[derive(Default)] pub struct KernelProgramInfo<'a> { pub kernel_component: Option<&'a mut TypedDataBase<'a>>, // TypedDataBasePtr pub string_offsets: Option<&'a mut TypedData<'a>>, // TypedDataPtr @@ -95,25 +106,30 @@ pub struct KernelProgramInfo<'a> { pub classes_cache: Option<&'a mut Array<'a>>, // ArrayPtr } +#[derive(Default)] pub struct ExceptionHandlers<'a> { pub handled_types_data: Option<&'a mut Array<'a>>, // ArrayPtr pub packed_fields: u32, } +#[derive(Default)] pub struct Context<'a> { pub parent: Option<&'a mut Context<'a>>, // ContextPtr pub num_variables: i32, } +#[derive(Default)] pub struct UnlinkedCall { pub can_patch_to_monomorphic: bool, } -pub struct String { +#[derive(Default)] +pub struct String_ { pub hash: Smi, // Smi pub length: Smi, // Smi } +#[derive(Default)] pub struct Class<'a> { pub name: String, // StringPtr -> Raw String pub user_name: String, // StringPtr -> Raw String @@ -146,6 +162,7 @@ pub struct Class<'a> { pub kernel_offset: i32, } +#[derive(Default)] pub struct Function<'a> { pub name: String, // StringPtr -> Raw String pub owner: Option<&'a mut Object<'a>>, // ObjectPtr @@ -160,6 +177,7 @@ pub struct Function<'a> { pub kind_tag: u32, } +#[derive(Default)] pub struct Library<'a> { pub name: String, // StringPtr -> Raw String pub url: String, // StringPtr -> Raw String @@ -180,6 +198,7 @@ pub struct Library<'a> { pub kernel_library_index: i32, } +#[derive(Default)] pub struct ContextScope { pub num_variables: i32, pub is_implicit: bool, @@ -189,11 +208,22 @@ pub struct ContextScope { // These classes either have no additional payload fields beyond the standard // instance headers or their payloads are purely variable-length or dynamically read/overlayed on top of a byte stream +#[derive(Default)] pub struct CodeSourceMap; + +#[derive(Default)] pub struct CompressedStackMaps; + +#[derive(Default)] pub struct PcDescriptors; + +#[derive(Default)] pub struct ObjectPool; + +#[derive(Default)] pub struct OneByteString; + +#[derive(Default)] pub struct TwoByteString; // Placeholder structs for references used above that aren't defined yet From 32b99ec2c960bba2910100e9c853e075bac452c7 Mon Sep 17 00:00:00 2001 From: kalixtez Date: Mon, 27 Apr 2026 14:38:00 -0500 Subject: [PATCH 12/50] feat(serwalker): derive Default on current classes --- .../src/raw_object/mod.rs | 34 +++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/crates/flutterdec-serwalker/src/raw_object/mod.rs b/crates/flutterdec-serwalker/src/raw_object/mod.rs index 2b7c920..23e5222 100644 --- a/crates/flutterdec-serwalker/src/raw_object/mod.rs +++ b/crates/flutterdec-serwalker/src/raw_object/mod.rs @@ -1,17 +1,19 @@ -// raw_object/mod.rs type Smi = i64; // Using i64 for Smi fields to be decompressed // --- Fixed-Size Objects with defined fields --- +#[derive(Default)] pub struct Mint { pub value: i64, } +#[derive(Default)] pub struct Double { pub value: f64, } +#[derive(Default)] pub struct TypeArguments<'a> { pub instantiations: Option<&'a mut Array<'a>>, // ArrayPtr pub length: Smi, // Smi @@ -19,16 +21,19 @@ pub struct TypeArguments<'a> { pub nullability: Smi, // Smi } +#[derive(Default)] pub struct TypeParameter<'a> { pub owner: Option<&'a mut Object<'a>>, // ObjectPtr pub base: i16, pub index: i16, } +#[derive(Default)] pub struct Type<'a> { pub arguments: Option<&'a mut TypeArguments<'a>>, // TypeArgumentsPtr } +#[derive(Default)] pub struct TypeParameters<'a> { pub names: Option<&'a mut Array<'a>>, // ArrayPtr pub flags: Option<&'a mut Array<'a>>, // ArrayPtr @@ -36,12 +41,14 @@ pub struct TypeParameters<'a> { pub defaults: Option<&'a mut TypeArguments<'a>>, // TypeArgumentsPtr } +#[derive(Default)] pub struct PatchClass<'a> { pub wrapped_class: Option<&'a mut Class<'a>>, // ClassPtr pub script: Option<&'a mut Script<'a>>, // ScriptPtr pub kernel_program_info: Option<&'a mut KernelProgramInfo<'a>>, // KernelProgramInfoPtr } +#[derive(Default)] pub struct ClosureData<'a> { pub context_scope: Option<&'a mut ContextScope>, // ContextScopePtr pub parent_function: Option<&'a mut Function<'a>>, // FunctionPtr @@ -49,6 +56,7 @@ pub struct ClosureData<'a> { pub packed_fields: u32, } +#[derive(Default)] pub struct FfiTrampolineData<'a> { pub signature_type: Option<&'a mut Type<'a>>, // TypePtr pub c_signature: Option<&'a mut FunctionType<'a>>, // FunctionTypePtr @@ -58,6 +66,7 @@ pub struct FfiTrampolineData<'a> { pub callback_id: i32, } +#[derive(Default)] pub struct Field<'a> { pub name: String, // StringPtr -> Raw String pub owner: Option<&'a mut Object<'a>>, // ObjectPtr @@ -74,6 +83,7 @@ pub struct Field<'a> { pub kind_bits: u32, } +#[derive(Default)] pub struct Namespace<'a> { pub target: Option<&'a mut Library<'a>>, // LibraryPtr pub show_names: Option<&'a mut Array<'a>>, // ArrayPtr @@ -81,6 +91,7 @@ pub struct Namespace<'a> { pub owner: Option<&'a mut Library<'a>>, // LibraryPtr } +#[derive(Default)] pub struct KernelProgramInfo<'a> { pub kernel_component: Option<&'a mut TypedDataBase<'a>>, // TypedDataBasePtr pub string_offsets: Option<&'a mut TypedData<'a>>, // TypedDataPtr @@ -95,25 +106,30 @@ pub struct KernelProgramInfo<'a> { pub classes_cache: Option<&'a mut Array<'a>>, // ArrayPtr } +#[derive(Default)] pub struct ExceptionHandlers<'a> { pub handled_types_data: Option<&'a mut Array<'a>>, // ArrayPtr pub packed_fields: u32, } +#[derive(Default)] pub struct Context<'a> { pub parent: Option<&'a mut Context<'a>>, // ContextPtr pub num_variables: i32, } +#[derive(Default)] pub struct UnlinkedCall { pub can_patch_to_monomorphic: bool, } -pub struct String { +#[derive(Default)] +pub struct String_ { pub hash: Smi, // Smi pub length: Smi, // Smi } +#[derive(Default)] pub struct Class<'a> { pub name: String, // StringPtr -> Raw String pub user_name: String, // StringPtr -> Raw String @@ -146,6 +162,7 @@ pub struct Class<'a> { pub kernel_offset: i32, } +#[derive(Default)] pub struct Function<'a> { pub name: String, // StringPtr -> Raw String pub owner: Option<&'a mut Object<'a>>, // ObjectPtr @@ -160,6 +177,7 @@ pub struct Function<'a> { pub kind_tag: u32, } +#[derive(Default)] pub struct Library<'a> { pub name: String, // StringPtr -> Raw String pub url: String, // StringPtr -> Raw String @@ -180,6 +198,7 @@ pub struct Library<'a> { pub kernel_library_index: i32, } +#[derive(Default)] pub struct ContextScope { pub num_variables: i32, pub is_implicit: bool, @@ -189,11 +208,22 @@ pub struct ContextScope { // These classes either have no additional payload fields beyond the standard // instance headers or their payloads are purely variable-length or dynamically read/overlayed on top of a byte stream +#[derive(Default)] pub struct CodeSourceMap; + +#[derive(Default)] pub struct CompressedStackMaps; + +#[derive(Default)] pub struct PcDescriptors; + +#[derive(Default)] pub struct ObjectPool; + +#[derive(Default)] pub struct OneByteString; + +#[derive(Default)] pub struct TwoByteString; // Placeholder structs for references used above that aren't defined yet From a6381a0bf70cdd413b9f4eafe8eae8c1e2fcca6a Mon Sep 17 00:00:00 2001 From: kalixtez Date: Mon, 27 Apr 2026 14:39:57 -0500 Subject: [PATCH 13/50] comment --- crates/flutterdec-serwalker/src/raw_object/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/flutterdec-serwalker/src/raw_object/mod.rs b/crates/flutterdec-serwalker/src/raw_object/mod.rs index 23e5222..24c33eb 100644 --- a/crates/flutterdec-serwalker/src/raw_object/mod.rs +++ b/crates/flutterdec-serwalker/src/raw_object/mod.rs @@ -124,7 +124,7 @@ pub struct UnlinkedCall { } #[derive(Default)] -pub struct String_ { +pub struct String_ { // added underscore so there's no conflict between this type and rust's String pub hash: Smi, // Smi pub length: Smi, // Smi } From 86bf19a846ff40e6f913818ca8d81fc1379cb76f Mon Sep 17 00:00:00 2001 From: kalixtez Date: Mon, 27 Apr 2026 14:39:57 -0500 Subject: [PATCH 14/50] docs(serwalker): add comment --- crates/flutterdec-serwalker/src/raw_object/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/flutterdec-serwalker/src/raw_object/mod.rs b/crates/flutterdec-serwalker/src/raw_object/mod.rs index 23e5222..24c33eb 100644 --- a/crates/flutterdec-serwalker/src/raw_object/mod.rs +++ b/crates/flutterdec-serwalker/src/raw_object/mod.rs @@ -124,7 +124,7 @@ pub struct UnlinkedCall { } #[derive(Default)] -pub struct String_ { +pub struct String_ { // added underscore so there's no conflict between this type and rust's String pub hash: Smi, // Smi pub length: Smi, // Smi } From ac2a614f7eb9a1f6a568ba5ca7fe6cd7be895002 Mon Sep 17 00:00:00 2001 From: kalixtez Date: Mon, 27 Apr 2026 14:54:11 -0500 Subject: [PATCH 15/50] format changes using cargo fmt --all --- .../flutterdec-serwalker/src/cluster/mod.rs | 32 ++-- crates/flutterdec-serwalker/src/constants.rs | 2 +- crates/flutterdec-serwalker/src/lib.rs | 4 +- .../src/raw_object/mod.rs | 180 ++++++++++-------- crates/flutterdec-serwalker/src/snapshot.rs | 39 ++-- crates/flutterdec-serwalker/src/stream.rs | 88 ++++----- crates/flutterdec-serwalker/src/utils.rs | 11 +- 7 files changed, 188 insertions(+), 168 deletions(-) diff --git a/crates/flutterdec-serwalker/src/cluster/mod.rs b/crates/flutterdec-serwalker/src/cluster/mod.rs index 568bdb5..c1559ca 100644 --- a/crates/flutterdec-serwalker/src/cluster/mod.rs +++ b/crates/flutterdec-serwalker/src/cluster/mod.rs @@ -1,36 +1,34 @@ -use crate::{constants, stream::Stream}; -use crate::DECLARE_FIXED_LENGTH_CLUSTER; -use crate::raw_object::*; use crate::constants::{ClassId, ClassId::*, SIGNED_M, UNSIGNED_M}; +use crate::raw_object::*; +use crate::DECLARE_FIXED_LENGTH_CLUSTER; +use crate::{constants, stream::Stream}; type Smi = i32; -pub trait Cluster -{ +pub trait Cluster { fn is_fixed_len(&self) -> bool; fn read_alloc(&mut self, last_ref_id: &mut u64, stream: &mut Stream) -> usize; fn read_fill(&mut self, last_ref_id: &mut u64, stream: &mut Stream) -> usize; } -pub fn read_cluster_alloc() -{ +pub fn read_cluster_alloc() { let curr_ref_id: u64 = 0; } -pub fn read_cluster_fill() -{ +pub fn read_cluster_fill() { let curr_ref_id: u64 = 0; } -pub fn read_and_decompress_smi(stream: &mut Stream) -> Smi -{ +pub fn read_and_decompress_smi(stream: &mut Stream) -> Smi { let raw_smi = stream.read_modified_leb128(UNSIGNED_M); // smis are always written as signed - + (raw_smi as Smi) >> constants::SMI_SHIFT } -pub fn decide_cluster(clusters: &mut [Box; constants::MAX_CLUSTER_NUM], class_id: ClassId) -> Result, &str> -{ +pub fn decide_cluster( + clusters: &mut [Box; constants::MAX_CLUSTER_NUM], + class_id: ClassId, +) -> Result, &str> { match class_id { IllegalCid => Err("Not a supported class (illegal class)..."), _ => Err("Not a supported class..."), @@ -41,8 +39,8 @@ pub fn decide_cluster(clusters: &mut [Box; constants::MAX_CLUSTER_N // whose fill cluster size is uniquely determined by sizeof(Object) * num_of_objects // and alloc cluster size is tags (MULEB128) + num_of_objects (MULEB128) - -DECLARE_FIXED_LENGTH_CLUSTER!(OneByteString, { 1 +DECLARE_FIXED_LENGTH_CLUSTER!(OneByteString, { + 1 // to-do }); /* @@ -53,4 +51,4 @@ DECLARE_FIXED_LENGTH_CLUSTER!(Double, 16); DECLARE_FIXED_LENGTH_CLUSTER!(TypeParameter, 32); DECLARE_FIXED_LENGTH_CLUSTER!(Type, 32); DECLARE_FIXED_LENGTH_CLUSTER!(TypeArguments, 32); -*/ \ No newline at end of file +*/ diff --git a/crates/flutterdec-serwalker/src/constants.rs b/crates/flutterdec-serwalker/src/constants.rs index 63b35c6..6a68d72 100644 --- a/crates/flutterdec-serwalker/src/constants.rs +++ b/crates/flutterdec-serwalker/src/constants.rs @@ -219,4 +219,4 @@ pub const CLUSTER_TAGS_SZ: usize = size_of::(); pub const CLUSTER_OBJ_COUNT_SZ: usize = size_of::(); pub const OBJECT_STORE_ENTRY_SIZE: usize = size_of::(); -*/ \ No newline at end of file +*/ diff --git a/crates/flutterdec-serwalker/src/lib.rs b/crates/flutterdec-serwalker/src/lib.rs index 1f91630..b5aaf10 100644 --- a/crates/flutterdec-serwalker/src/lib.rs +++ b/crates/flutterdec-serwalker/src/lib.rs @@ -1,8 +1,8 @@ -mod utils; mod constants; +mod utils; mod cluster; mod raw_object; mod stream; -mod snapshot; \ No newline at end of file +mod snapshot; diff --git a/crates/flutterdec-serwalker/src/raw_object/mod.rs b/crates/flutterdec-serwalker/src/raw_object/mod.rs index 24c33eb..3692170 100644 --- a/crates/flutterdec-serwalker/src/raw_object/mod.rs +++ b/crates/flutterdec-serwalker/src/raw_object/mod.rs @@ -1,4 +1,3 @@ - type Smi = i64; // Using i64 for Smi fields to be decompressed // --- Fixed-Size Objects with defined fields --- @@ -43,24 +42,24 @@ pub struct TypeParameters<'a> { #[derive(Default)] pub struct PatchClass<'a> { - pub wrapped_class: Option<&'a mut Class<'a>>, // ClassPtr - pub script: Option<&'a mut Script<'a>>, // ScriptPtr + pub wrapped_class: Option<&'a mut Class<'a>>, // ClassPtr + pub script: Option<&'a mut Script<'a>>, // ScriptPtr pub kernel_program_info: Option<&'a mut KernelProgramInfo<'a>>, // KernelProgramInfoPtr } #[derive(Default)] pub struct ClosureData<'a> { - pub context_scope: Option<&'a mut ContextScope>, // ContextScopePtr + pub context_scope: Option<&'a mut ContextScope>, // ContextScopePtr pub parent_function: Option<&'a mut Function<'a>>, // FunctionPtr - pub closure: Option<&'a mut Closure<'a>>, // ClosurePtr + pub closure: Option<&'a mut Closure<'a>>, // ClosurePtr pub packed_fields: u32, } #[derive(Default)] pub struct FfiTrampolineData<'a> { - pub signature_type: Option<&'a mut Type<'a>>, // TypePtr - pub c_signature: Option<&'a mut FunctionType<'a>>, // FunctionTypePtr - pub callback_target: Option<&'a mut Function<'a>>, // FunctionPtr + pub signature_type: Option<&'a mut Type<'a>>, // TypePtr + pub c_signature: Option<&'a mut FunctionType<'a>>, // FunctionTypePtr + pub callback_target: Option<&'a mut Function<'a>>, // FunctionPtr pub callback_exceptional_return: Option<&'a mut Instance<'a>>, // InstancePtr pub ffi_function_kind: u8, pub callback_id: i32, @@ -68,14 +67,14 @@ pub struct FfiTrampolineData<'a> { #[derive(Default)] pub struct Field<'a> { - pub name: String, // StringPtr -> Raw String - pub owner: Option<&'a mut Object<'a>>, // ObjectPtr - pub type_field: Option<&'a mut AbstractType<'a>>, // AbstractTypePtr + pub name: String, // StringPtr -> Raw String + pub owner: Option<&'a mut Object<'a>>, // ObjectPtr + pub type_field: Option<&'a mut AbstractType<'a>>, // AbstractTypePtr pub initializer_function: Option<&'a mut Function<'a>>, // FunctionPtr - pub host_offset_or_field_id: Smi, // Smi - pub guarded_list_length: Smi, // Smi - pub exact_type: Option<&'a mut AbstractType<'a>>, // AbstractTypePtr - pub dependent_code: Option<&'a mut WeakArray<'a>>, // WeakArrayPtr + pub host_offset_or_field_id: Smi, // Smi + pub guarded_list_length: Smi, // Smi + pub exact_type: Option<&'a mut AbstractType<'a>>, // AbstractTypePtr + pub dependent_code: Option<&'a mut WeakArray<'a>>, // WeakArrayPtr pub kernel_offset: i32, pub guarded_list_length_in_object_offset: i8, pub static_type_exactness_state: i8, @@ -85,25 +84,25 @@ pub struct Field<'a> { #[derive(Default)] pub struct Namespace<'a> { - pub target: Option<&'a mut Library<'a>>, // LibraryPtr - pub show_names: Option<&'a mut Array<'a>>, // ArrayPtr - pub hide_names: Option<&'a mut Array<'a>>, // ArrayPtr - pub owner: Option<&'a mut Library<'a>>, // LibraryPtr + pub target: Option<&'a mut Library<'a>>, // LibraryPtr + pub show_names: Option<&'a mut Array<'a>>, // ArrayPtr + pub hide_names: Option<&'a mut Array<'a>>, // ArrayPtr + pub owner: Option<&'a mut Library<'a>>, // LibraryPtr } #[derive(Default)] pub struct KernelProgramInfo<'a> { - pub kernel_component: Option<&'a mut TypedDataBase<'a>>, // TypedDataBasePtr - pub string_offsets: Option<&'a mut TypedData<'a>>, // TypedDataPtr - pub string_data: Option<&'a mut TypedDataView<'a>>, // TypedDataViewPtr - pub canonical_names: Option<&'a mut TypedData<'a>>, // TypedDataPtr + pub kernel_component: Option<&'a mut TypedDataBase<'a>>, // TypedDataBasePtr + pub string_offsets: Option<&'a mut TypedData<'a>>, // TypedDataPtr + pub string_data: Option<&'a mut TypedDataView<'a>>, // TypedDataViewPtr + pub canonical_names: Option<&'a mut TypedData<'a>>, // TypedDataPtr pub metadata_payloads: Option<&'a mut TypedDataView<'a>>, // TypedDataViewPtr pub metadata_mappings: Option<&'a mut TypedDataView<'a>>, // TypedDataViewPtr - pub scripts: Option<&'a mut Array<'a>>, // ArrayPtr - pub constants: Option<&'a mut Array<'a>>, // ArrayPtr - pub constants_table: Option<&'a mut TypedDataView<'a>>, // TypedDataViewPtr - pub libraries_cache: Option<&'a mut Array<'a>>, // ArrayPtr - pub classes_cache: Option<&'a mut Array<'a>>, // ArrayPtr + pub scripts: Option<&'a mut Array<'a>>, // ArrayPtr + pub constants: Option<&'a mut Array<'a>>, // ArrayPtr + pub constants_table: Option<&'a mut TypedDataView<'a>>, // TypedDataViewPtr + pub libraries_cache: Option<&'a mut Array<'a>>, // ArrayPtr + pub classes_cache: Option<&'a mut Array<'a>>, // ArrayPtr } #[derive(Default)] @@ -124,32 +123,33 @@ pub struct UnlinkedCall { } #[derive(Default)] -pub struct String_ { // added underscore so there's no conflict between this type and rust's String +pub struct String_ { + // added underscore so there's no conflict between this type and rust's String pub hash: Smi, // Smi pub length: Smi, // Smi } #[derive(Default)] pub struct Class<'a> { - pub name: String, // StringPtr -> Raw String - pub user_name: String, // StringPtr -> Raw String - pub functions: Option<&'a mut Array<'a>>, // ArrayPtr - pub functions_hash_table: Option<&'a mut Array<'a>>, // ArrayPtr - pub fields: Option<&'a mut Array<'a>>, // ArrayPtr - pub offset_in_words_to_field: Option<&'a mut Array<'a>>, // ArrayPtr - pub interfaces: Option<&'a mut Array<'a>>, // ArrayPtr - pub script: Option<&'a mut Script<'a>>, // ScriptPtr - pub library: Option<&'a mut Library<'a>>, // LibraryPtr - pub type_parameters: Option<&'a mut TypeParameters<'a>>, // TypeParametersPtr - pub super_type: Option<&'a mut Type<'a>>, // TypePtr - pub constants: Option<&'a mut Array<'a>>, // ArrayPtr - pub declaration_type: Option<&'a mut Type<'a>>, // TypePtr - pub invocation_dispatcher_cache: Option<&'a mut Array<'a>>, // ArrayPtr + pub name: String, // StringPtr -> Raw String + pub user_name: String, // StringPtr -> Raw String + pub functions: Option<&'a mut Array<'a>>, // ArrayPtr + pub functions_hash_table: Option<&'a mut Array<'a>>, // ArrayPtr + pub fields: Option<&'a mut Array<'a>>, // ArrayPtr + pub offset_in_words_to_field: Option<&'a mut Array<'a>>, // ArrayPtr + pub interfaces: Option<&'a mut Array<'a>>, // ArrayPtr + pub script: Option<&'a mut Script<'a>>, // ScriptPtr + pub library: Option<&'a mut Library<'a>>, // LibraryPtr + pub type_parameters: Option<&'a mut TypeParameters<'a>>, // TypeParametersPtr + pub super_type: Option<&'a mut Type<'a>>, // TypePtr + pub constants: Option<&'a mut Array<'a>>, // ArrayPtr + pub declaration_type: Option<&'a mut Type<'a>>, // TypePtr + pub invocation_dispatcher_cache: Option<&'a mut Array<'a>>, // ArrayPtr pub direct_implementors: Option<&'a mut GrowableObjectArray<'a>>, // GrowableObjectArrayPtr pub direct_subclasses: Option<&'a mut GrowableObjectArray<'a>>, // GrowableObjectArrayPtr pub declaration_instance_type_arguments: Option<&'a mut TypeArguments<'a>>, // TypeArgumentsPtr - pub allocation_stub: Option<&'a mut Code<'a>>, // CodePtr - pub dependent_code: Option<&'a mut WeakArray<'a>>, // WeakArrayPtr + pub allocation_stub: Option<&'a mut Code<'a>>, // CodePtr + pub dependent_code: Option<&'a mut WeakArray<'a>>, // WeakArrayPtr pub num_type_arguments: i16, pub num_native_fields: u16, pub state_bits: u32, @@ -164,14 +164,14 @@ pub struct Class<'a> { #[derive(Default)] pub struct Function<'a> { - pub name: String, // StringPtr -> Raw String - pub owner: Option<&'a mut Object<'a>>, // ObjectPtr - pub signature: Option<&'a mut FunctionType<'a>>, // FunctionTypePtr - pub data: Option<&'a mut Object<'a>>, // ObjectPtr + pub name: String, // StringPtr -> Raw String + pub owner: Option<&'a mut Object<'a>>, // ObjectPtr + pub signature: Option<&'a mut FunctionType<'a>>, // FunctionTypePtr + pub data: Option<&'a mut Object<'a>>, // ObjectPtr pub ic_data_array_or_bytecode: Option<&'a mut Object<'a>>, // ObjectPtr - pub code: Option<&'a mut Code<'a>>, // CodePtr + pub code: Option<&'a mut Code<'a>>, // CodePtr pub positional_parameter_names: Option<&'a mut Array<'a>>, // ArrayPtr - pub unoptimized_code: Option<&'a mut Code<'a>>, // CodePtr + pub unoptimized_code: Option<&'a mut Code<'a>>, // CodePtr pub bitmap: u64, pub kernel_offset: i32, pub kind_tag: u32, @@ -179,19 +179,19 @@ pub struct Function<'a> { #[derive(Default)] pub struct Library<'a> { - pub name: String, // StringPtr -> Raw String - pub url: String, // StringPtr -> Raw String - pub private_key: String, // StringPtr -> Raw String - pub dictionary: Option<&'a mut Array<'a>>, // ArrayPtr - pub metadata: Option<&'a mut Array<'a>>, // ArrayPtr - pub toplevel_class: Option<&'a mut Class<'a>>, // ClassPtr + pub name: String, // StringPtr -> Raw String + pub url: String, // StringPtr -> Raw String + pub private_key: String, // StringPtr -> Raw String + pub dictionary: Option<&'a mut Array<'a>>, // ArrayPtr + pub metadata: Option<&'a mut Array<'a>>, // ArrayPtr + pub toplevel_class: Option<&'a mut Class<'a>>, // ClassPtr pub used_scripts: Option<&'a mut GrowableObjectArray<'a>>, // GrowableObjectArrayPtr - pub loading_unit: Option<&'a mut LoadingUnit<'a>>, // LoadingUnitPtr - pub imports: Option<&'a mut Array<'a>>, // ArrayPtr - pub exports: Option<&'a mut Array<'a>>, // ArrayPtr - pub dependencies: Option<&'a mut Array<'a>>, // ArrayPtr + pub loading_unit: Option<&'a mut LoadingUnit<'a>>, // LoadingUnitPtr + pub imports: Option<&'a mut Array<'a>>, // ArrayPtr + pub exports: Option<&'a mut Array<'a>>, // ArrayPtr + pub dependencies: Option<&'a mut Array<'a>>, // ArrayPtr pub kernel_program_info: Option<&'a mut KernelProgramInfo<'a>>, // KernelProgramInfoPtr - pub loaded_scripts: Option<&'a mut Array<'a>>, // ArrayPtr + pub loaded_scripts: Option<&'a mut Array<'a>>, // ArrayPtr pub num_imports: u16, pub load_state: i8, pub flags: u8, @@ -228,17 +228,45 @@ pub struct TwoByteString; // Placeholder structs for references used above that aren't defined yet // it wouldn't compile without this, though for now they remain unimplemented... -pub struct Array<'a> { _marker: std::marker::PhantomData<&'a ()> } -pub struct Object<'a> { _marker: std::marker::PhantomData<&'a ()> } -pub struct AbstractType<'a> { _marker: std::marker::PhantomData<&'a ()> } -pub struct FunctionType<'a> { _marker: std::marker::PhantomData<&'a ()> } -pub struct Script<'a> { _marker: std::marker::PhantomData<&'a ()> } -pub struct Closure<'a> { _marker: std::marker::PhantomData<&'a ()> } -pub struct Instance<'a> { _marker: std::marker::PhantomData<&'a ()> } -pub struct WeakArray<'a> { _marker: std::marker::PhantomData<&'a ()> } -pub struct TypedDataBase<'a> { _marker: std::marker::PhantomData<&'a ()> } -pub struct TypedData<'a> { _marker: std::marker::PhantomData<&'a ()> } -pub struct TypedDataView<'a> { _marker: std::marker::PhantomData<&'a ()> } -pub struct GrowableObjectArray<'a> { _marker: std::marker::PhantomData<&'a ()> } -pub struct Code<'a> { _marker: std::marker::PhantomData<&'a ()> } -pub struct LoadingUnit<'a> { _marker: std::marker::PhantomData<&'a ()> } +pub struct Array<'a> { + _marker: std::marker::PhantomData<&'a ()>, +} +pub struct Object<'a> { + _marker: std::marker::PhantomData<&'a ()>, +} +pub struct AbstractType<'a> { + _marker: std::marker::PhantomData<&'a ()>, +} +pub struct FunctionType<'a> { + _marker: std::marker::PhantomData<&'a ()>, +} +pub struct Script<'a> { + _marker: std::marker::PhantomData<&'a ()>, +} +pub struct Closure<'a> { + _marker: std::marker::PhantomData<&'a ()>, +} +pub struct Instance<'a> { + _marker: std::marker::PhantomData<&'a ()>, +} +pub struct WeakArray<'a> { + _marker: std::marker::PhantomData<&'a ()>, +} +pub struct TypedDataBase<'a> { + _marker: std::marker::PhantomData<&'a ()>, +} +pub struct TypedData<'a> { + _marker: std::marker::PhantomData<&'a ()>, +} +pub struct TypedDataView<'a> { + _marker: std::marker::PhantomData<&'a ()>, +} +pub struct GrowableObjectArray<'a> { + _marker: std::marker::PhantomData<&'a ()>, +} +pub struct Code<'a> { + _marker: std::marker::PhantomData<&'a ()>, +} +pub struct LoadingUnit<'a> { + _marker: std::marker::PhantomData<&'a ()>, +} diff --git a/crates/flutterdec-serwalker/src/snapshot.rs b/crates/flutterdec-serwalker/src/snapshot.rs index f109274..18cdf3a 100644 --- a/crates/flutterdec-serwalker/src/snapshot.rs +++ b/crates/flutterdec-serwalker/src/snapshot.rs @@ -1,16 +1,17 @@ -use crate::cluster::{ Cluster }; -use crate::stream::{ Stream }; +use crate::cluster::Cluster; use crate::constants::{self, MAGIC_BYTES}; +use crate::stream::Stream; -enum SnapshotKind // pulled straight out of the C++ def +enum SnapshotKind +// pulled straight out of the C++ def { - Full, - FullCore, - FullJIT, - FullAOT, // Full + AOT code, this is the one we care about, as th - Module, - None, - Invalid + Full, + FullCore, + FullJIT, + FullAOT, // Full + AOT code, this is the one we care about, as th + Module, + None, + Invalid, } impl TryFrom for SnapshotKind { @@ -29,8 +30,7 @@ impl TryFrom for SnapshotKind { } } } -struct DataSnapshot -{ +struct DataSnapshot { // this array will contain mutable references to all clusters, and it will be indexed using the class id clusters: [Box; constants::MAX_CLUSTER_NUM], // thus each cluster must have its own UNIQUE class id @@ -53,12 +53,10 @@ struct DataSnapshot } impl DataSnapshot { - fn parse_header(&mut self, stream: &mut Stream) - { + fn parse_header(&mut self, stream: &mut Stream) { self.magic_bytes = stream.read_u32(); - if self.magic_bytes != MAGIC_BYTES - { + if self.magic_bytes != MAGIC_BYTES { panic!("Not a snapshot...") } @@ -68,17 +66,14 @@ impl DataSnapshot { self.parse_version_and_features(stream); } - fn parse_version_and_features(&mut self, stream: &mut Stream) - { + fn parse_version_and_features(&mut self, stream: &mut Stream) { let mut version_and_features = stream.read_c_string(); self.features = version_and_features.split_off(constants::VERSION_HASH_LENGTH); // returns (str[hash_len..]) self.version_hash = version_and_features; } - fn read_clusters(&mut self, stream: &Stream) - { + fn read_clusters(&mut self, stream: &Stream) { let curr_ref_id: u64 = 0; // all objects are numbered starting from 0 - } -} \ No newline at end of file +} diff --git a/crates/flutterdec-serwalker/src/stream.rs b/crates/flutterdec-serwalker/src/stream.rs index 2a9c45d..a0c52e4 100644 --- a/crates/flutterdec-serwalker/src/stream.rs +++ b/crates/flutterdec-serwalker/src/stream.rs @@ -1,40 +1,42 @@ -use crate::constants::{UNSIGNED_END_OF_DATA_BYTE, SIGNED_END_OF_DATA_BYTE, UNSIGNED_MAX_DATA_PER_BYTE, DATA_BITS_PER_BYTE}; -pub struct Stream<'a> -{ +use crate::constants::{ + DATA_BITS_PER_BYTE, SIGNED_END_OF_DATA_BYTE, UNSIGNED_END_OF_DATA_BYTE, + UNSIGNED_MAX_DATA_PER_BYTE, +}; +pub struct Stream<'a> { byte_stream: &'a [u8], curr_stream_offset: usize, } -impl<'a> Stream<'a> -{ +impl<'a> Stream<'a> { fn seek(&mut self, pos: usize) // might be useful? { - if self.byte_stream.len() > pos && pos > 0 { self.curr_stream_offset = pos; } + if self.byte_stream.len() > pos && pos > 0 { + self.curr_stream_offset = pos; + } } - pub fn advance_pos(&mut self, num_bytes: usize) - { + pub fn advance_pos(&mut self, num_bytes: usize) { self.curr_stream_offset += num_bytes; } - pub fn get_current_pos(&self) -> usize - { + pub fn get_current_pos(&self) -> usize { self.curr_stream_offset } /* - Reads a modified uleb from the current stream offset. + Reads a modified uleb from the current stream offset. - Dart uses a modified LEB128 format. The normal format uses bytes with their MSb set in order - to signify that there are more bytes ahead, and the last byte has its MSb unset, whereas Dart's - implementation does the opposite. The "continuation" bit on each byte is 0, and the last byte - has its MSb set. - */ + Dart uses a modified LEB128 format. The normal format uses bytes with their MSb set in order + to signify that there are more bytes ahead, and the last byte has its MSb unset, whereas Dart's + implementation does the opposite. The "continuation" bit on each byte is 0, and the last byte + has its MSb set. + */ pub fn read_modified_leb128(&mut self, sign_marker: u8) -> u64 // 8 bytes should be enough for anything... { let mut idx: u8 = 0; - let first_byte = self.byte_stream[self.curr_stream_offset]; - if first_byte > UNSIGNED_MAX_DATA_PER_BYTE // if the first byte has its MSb set + let first_byte = self.byte_stream[self.curr_stream_offset]; + if first_byte > UNSIGNED_MAX_DATA_PER_BYTE + // if the first byte has its MSb set { self.advance_pos(1); // wrapping_sub mimics C++ unsigned underflow, giving us perfect sign-extension @@ -47,7 +49,9 @@ impl<'a> Stream<'a> loop { byte = self.byte_stream[self.curr_stream_offset + idx as usize]; - if byte & UNSIGNED_END_OF_DATA_BYTE == UNSIGNED_END_OF_DATA_BYTE { break; } // final byte + if byte & UNSIGNED_END_OF_DATA_BYTE == UNSIGNED_END_OF_DATA_BYTE { + break; + } // final byte read_num |= (byte as u64) << (idx as usize * DATA_BITS_PER_BYTE); idx += 1; } @@ -61,28 +65,24 @@ impl<'a> Stream<'a> read_num } - pub fn read_u64(&mut self) -> u64 - { + pub fn read_u64(&mut self) -> u64 { let u64_size = std::mem::size_of::(); - let num_slice = &self.byte_stream[self.curr_stream_offset..self.curr_stream_offset + u64_size]; + let num_slice = + &self.byte_stream[self.curr_stream_offset..self.curr_stream_offset + u64_size]; - let converted_slice: [u8; 8] = num_slice - .try_into() - .expect("Slice wasn't 8 bytes long..."); + let converted_slice: [u8; 8] = num_slice.try_into().expect("Slice wasn't 8 bytes long..."); self.advance_pos(u64_size); u64::from_le_bytes(converted_slice) } - pub fn read_u32(&mut self) -> u32 - { + pub fn read_u32(&mut self) -> u32 { let u32_size = std::mem::size_of::(); - let num_slice = &self.byte_stream[self.curr_stream_offset..self.curr_stream_offset + u32_size]; + let num_slice = + &self.byte_stream[self.curr_stream_offset..self.curr_stream_offset + u32_size]; - let converted_slice: [u8; 4] = num_slice - .try_into() - .expect("Slice wasn't 4 bytes long..."); + let converted_slice: [u8; 4] = num_slice.try_into().expect("Slice wasn't 4 bytes long..."); self.advance_pos(u32_size); @@ -90,22 +90,22 @@ impl<'a> Stream<'a> } /* - Panics if it isn't possible to create a stream from the utf-8 representation stored in - the byte slice. It shouldn't happen, so the best possible outcome is to assume some - logic mistake has been made and end the application. It should be a good thing to change this to an - unwrap_or_else so that we can also print the stream offset and cluster where this error occurred, in order - to have static debug info. - */ - pub fn read_c_string(&mut self) -> String - { - let first_nullbyte_pos = self.byte_stream - .iter() - .position(|&b| b == 0x00) - .unwrap_or(self.byte_stream.len()); + Panics if it isn't possible to create a stream from the utf-8 representation stored in + the byte slice. It shouldn't happen, so the best possible outcome is to assume some + logic mistake has been made and end the application. It should be a good thing to change this to an + unwrap_or_else so that we can also print the stream offset and cluster where this error occurred, in order + to have static debug info. + */ + pub fn read_c_string(&mut self) -> String { + let first_nullbyte_pos = self + .byte_stream + .iter() + .position(|&b| b == 0x00) + .unwrap_or(self.byte_stream.len()); let raw_str = &self.byte_stream[..=first_nullbyte_pos]; self.advance_pos(raw_str.len()); String::from_utf8(raw_str.to_vec()).unwrap() // it should be horrible if for some reason a string just isn't there } -} \ No newline at end of file +} diff --git a/crates/flutterdec-serwalker/src/utils.rs b/crates/flutterdec-serwalker/src/utils.rs index 049d66f..4f7eb30 100644 --- a/crates/flutterdec-serwalker/src/utils.rs +++ b/crates/flutterdec-serwalker/src/utils.rs @@ -1,11 +1,10 @@ -use paste::paste; use crate::cluster::Cluster; -use crate::stream::Stream; use crate::constants::UNSIGNED_M; +use crate::stream::Stream; +use paste::paste; #[macro_export] -macro_rules! DECLARE_FIXED_LENGTH_CLUSTER -{ +macro_rules! DECLARE_FIXED_LENGTH_CLUSTER { ($name:ident, $fill_impl:block) => { ::paste::paste! { // this is ugly, but the language doesn't support identifier concatenation struct [<$name Cluster>] @@ -22,7 +21,7 @@ macro_rules! DECLARE_FIXED_LENGTH_CLUSTER objs: Vec<(u64, Box<$name>)> // a pair (ref_id, object) } - impl Cluster for [<$name Cluster>] + impl Cluster for [<$name Cluster>] { fn read_alloc(&mut self, last_ref_id: &mut u64, stream: &mut Stream) -> usize // read tags and count { @@ -51,4 +50,4 @@ macro_rules! DECLARE_FIXED_LENGTH_CLUSTER } } }; -} \ No newline at end of file +} From b36733ae4d7105ce3e276c11bb3a44f57b3cccff Mon Sep 17 00:00:00 2001 From: kalixtez Date: Mon, 27 Apr 2026 14:54:11 -0500 Subject: [PATCH 16/50] style: cargo fmt --all --- .../flutterdec-serwalker/src/cluster/mod.rs | 32 ++-- crates/flutterdec-serwalker/src/constants.rs | 2 +- crates/flutterdec-serwalker/src/lib.rs | 4 +- .../src/raw_object/mod.rs | 180 ++++++++++-------- crates/flutterdec-serwalker/src/snapshot.rs | 39 ++-- crates/flutterdec-serwalker/src/stream.rs | 88 ++++----- crates/flutterdec-serwalker/src/utils.rs | 11 +- 7 files changed, 188 insertions(+), 168 deletions(-) diff --git a/crates/flutterdec-serwalker/src/cluster/mod.rs b/crates/flutterdec-serwalker/src/cluster/mod.rs index 568bdb5..c1559ca 100644 --- a/crates/flutterdec-serwalker/src/cluster/mod.rs +++ b/crates/flutterdec-serwalker/src/cluster/mod.rs @@ -1,36 +1,34 @@ -use crate::{constants, stream::Stream}; -use crate::DECLARE_FIXED_LENGTH_CLUSTER; -use crate::raw_object::*; use crate::constants::{ClassId, ClassId::*, SIGNED_M, UNSIGNED_M}; +use crate::raw_object::*; +use crate::DECLARE_FIXED_LENGTH_CLUSTER; +use crate::{constants, stream::Stream}; type Smi = i32; -pub trait Cluster -{ +pub trait Cluster { fn is_fixed_len(&self) -> bool; fn read_alloc(&mut self, last_ref_id: &mut u64, stream: &mut Stream) -> usize; fn read_fill(&mut self, last_ref_id: &mut u64, stream: &mut Stream) -> usize; } -pub fn read_cluster_alloc() -{ +pub fn read_cluster_alloc() { let curr_ref_id: u64 = 0; } -pub fn read_cluster_fill() -{ +pub fn read_cluster_fill() { let curr_ref_id: u64 = 0; } -pub fn read_and_decompress_smi(stream: &mut Stream) -> Smi -{ +pub fn read_and_decompress_smi(stream: &mut Stream) -> Smi { let raw_smi = stream.read_modified_leb128(UNSIGNED_M); // smis are always written as signed - + (raw_smi as Smi) >> constants::SMI_SHIFT } -pub fn decide_cluster(clusters: &mut [Box; constants::MAX_CLUSTER_NUM], class_id: ClassId) -> Result, &str> -{ +pub fn decide_cluster( + clusters: &mut [Box; constants::MAX_CLUSTER_NUM], + class_id: ClassId, +) -> Result, &str> { match class_id { IllegalCid => Err("Not a supported class (illegal class)..."), _ => Err("Not a supported class..."), @@ -41,8 +39,8 @@ pub fn decide_cluster(clusters: &mut [Box; constants::MAX_CLUSTER_N // whose fill cluster size is uniquely determined by sizeof(Object) * num_of_objects // and alloc cluster size is tags (MULEB128) + num_of_objects (MULEB128) - -DECLARE_FIXED_LENGTH_CLUSTER!(OneByteString, { 1 +DECLARE_FIXED_LENGTH_CLUSTER!(OneByteString, { + 1 // to-do }); /* @@ -53,4 +51,4 @@ DECLARE_FIXED_LENGTH_CLUSTER!(Double, 16); DECLARE_FIXED_LENGTH_CLUSTER!(TypeParameter, 32); DECLARE_FIXED_LENGTH_CLUSTER!(Type, 32); DECLARE_FIXED_LENGTH_CLUSTER!(TypeArguments, 32); -*/ \ No newline at end of file +*/ diff --git a/crates/flutterdec-serwalker/src/constants.rs b/crates/flutterdec-serwalker/src/constants.rs index 63b35c6..6a68d72 100644 --- a/crates/flutterdec-serwalker/src/constants.rs +++ b/crates/flutterdec-serwalker/src/constants.rs @@ -219,4 +219,4 @@ pub const CLUSTER_TAGS_SZ: usize = size_of::(); pub const CLUSTER_OBJ_COUNT_SZ: usize = size_of::(); pub const OBJECT_STORE_ENTRY_SIZE: usize = size_of::(); -*/ \ No newline at end of file +*/ diff --git a/crates/flutterdec-serwalker/src/lib.rs b/crates/flutterdec-serwalker/src/lib.rs index 1f91630..b5aaf10 100644 --- a/crates/flutterdec-serwalker/src/lib.rs +++ b/crates/flutterdec-serwalker/src/lib.rs @@ -1,8 +1,8 @@ -mod utils; mod constants; +mod utils; mod cluster; mod raw_object; mod stream; -mod snapshot; \ No newline at end of file +mod snapshot; diff --git a/crates/flutterdec-serwalker/src/raw_object/mod.rs b/crates/flutterdec-serwalker/src/raw_object/mod.rs index 24c33eb..3692170 100644 --- a/crates/flutterdec-serwalker/src/raw_object/mod.rs +++ b/crates/flutterdec-serwalker/src/raw_object/mod.rs @@ -1,4 +1,3 @@ - type Smi = i64; // Using i64 for Smi fields to be decompressed // --- Fixed-Size Objects with defined fields --- @@ -43,24 +42,24 @@ pub struct TypeParameters<'a> { #[derive(Default)] pub struct PatchClass<'a> { - pub wrapped_class: Option<&'a mut Class<'a>>, // ClassPtr - pub script: Option<&'a mut Script<'a>>, // ScriptPtr + pub wrapped_class: Option<&'a mut Class<'a>>, // ClassPtr + pub script: Option<&'a mut Script<'a>>, // ScriptPtr pub kernel_program_info: Option<&'a mut KernelProgramInfo<'a>>, // KernelProgramInfoPtr } #[derive(Default)] pub struct ClosureData<'a> { - pub context_scope: Option<&'a mut ContextScope>, // ContextScopePtr + pub context_scope: Option<&'a mut ContextScope>, // ContextScopePtr pub parent_function: Option<&'a mut Function<'a>>, // FunctionPtr - pub closure: Option<&'a mut Closure<'a>>, // ClosurePtr + pub closure: Option<&'a mut Closure<'a>>, // ClosurePtr pub packed_fields: u32, } #[derive(Default)] pub struct FfiTrampolineData<'a> { - pub signature_type: Option<&'a mut Type<'a>>, // TypePtr - pub c_signature: Option<&'a mut FunctionType<'a>>, // FunctionTypePtr - pub callback_target: Option<&'a mut Function<'a>>, // FunctionPtr + pub signature_type: Option<&'a mut Type<'a>>, // TypePtr + pub c_signature: Option<&'a mut FunctionType<'a>>, // FunctionTypePtr + pub callback_target: Option<&'a mut Function<'a>>, // FunctionPtr pub callback_exceptional_return: Option<&'a mut Instance<'a>>, // InstancePtr pub ffi_function_kind: u8, pub callback_id: i32, @@ -68,14 +67,14 @@ pub struct FfiTrampolineData<'a> { #[derive(Default)] pub struct Field<'a> { - pub name: String, // StringPtr -> Raw String - pub owner: Option<&'a mut Object<'a>>, // ObjectPtr - pub type_field: Option<&'a mut AbstractType<'a>>, // AbstractTypePtr + pub name: String, // StringPtr -> Raw String + pub owner: Option<&'a mut Object<'a>>, // ObjectPtr + pub type_field: Option<&'a mut AbstractType<'a>>, // AbstractTypePtr pub initializer_function: Option<&'a mut Function<'a>>, // FunctionPtr - pub host_offset_or_field_id: Smi, // Smi - pub guarded_list_length: Smi, // Smi - pub exact_type: Option<&'a mut AbstractType<'a>>, // AbstractTypePtr - pub dependent_code: Option<&'a mut WeakArray<'a>>, // WeakArrayPtr + pub host_offset_or_field_id: Smi, // Smi + pub guarded_list_length: Smi, // Smi + pub exact_type: Option<&'a mut AbstractType<'a>>, // AbstractTypePtr + pub dependent_code: Option<&'a mut WeakArray<'a>>, // WeakArrayPtr pub kernel_offset: i32, pub guarded_list_length_in_object_offset: i8, pub static_type_exactness_state: i8, @@ -85,25 +84,25 @@ pub struct Field<'a> { #[derive(Default)] pub struct Namespace<'a> { - pub target: Option<&'a mut Library<'a>>, // LibraryPtr - pub show_names: Option<&'a mut Array<'a>>, // ArrayPtr - pub hide_names: Option<&'a mut Array<'a>>, // ArrayPtr - pub owner: Option<&'a mut Library<'a>>, // LibraryPtr + pub target: Option<&'a mut Library<'a>>, // LibraryPtr + pub show_names: Option<&'a mut Array<'a>>, // ArrayPtr + pub hide_names: Option<&'a mut Array<'a>>, // ArrayPtr + pub owner: Option<&'a mut Library<'a>>, // LibraryPtr } #[derive(Default)] pub struct KernelProgramInfo<'a> { - pub kernel_component: Option<&'a mut TypedDataBase<'a>>, // TypedDataBasePtr - pub string_offsets: Option<&'a mut TypedData<'a>>, // TypedDataPtr - pub string_data: Option<&'a mut TypedDataView<'a>>, // TypedDataViewPtr - pub canonical_names: Option<&'a mut TypedData<'a>>, // TypedDataPtr + pub kernel_component: Option<&'a mut TypedDataBase<'a>>, // TypedDataBasePtr + pub string_offsets: Option<&'a mut TypedData<'a>>, // TypedDataPtr + pub string_data: Option<&'a mut TypedDataView<'a>>, // TypedDataViewPtr + pub canonical_names: Option<&'a mut TypedData<'a>>, // TypedDataPtr pub metadata_payloads: Option<&'a mut TypedDataView<'a>>, // TypedDataViewPtr pub metadata_mappings: Option<&'a mut TypedDataView<'a>>, // TypedDataViewPtr - pub scripts: Option<&'a mut Array<'a>>, // ArrayPtr - pub constants: Option<&'a mut Array<'a>>, // ArrayPtr - pub constants_table: Option<&'a mut TypedDataView<'a>>, // TypedDataViewPtr - pub libraries_cache: Option<&'a mut Array<'a>>, // ArrayPtr - pub classes_cache: Option<&'a mut Array<'a>>, // ArrayPtr + pub scripts: Option<&'a mut Array<'a>>, // ArrayPtr + pub constants: Option<&'a mut Array<'a>>, // ArrayPtr + pub constants_table: Option<&'a mut TypedDataView<'a>>, // TypedDataViewPtr + pub libraries_cache: Option<&'a mut Array<'a>>, // ArrayPtr + pub classes_cache: Option<&'a mut Array<'a>>, // ArrayPtr } #[derive(Default)] @@ -124,32 +123,33 @@ pub struct UnlinkedCall { } #[derive(Default)] -pub struct String_ { // added underscore so there's no conflict between this type and rust's String +pub struct String_ { + // added underscore so there's no conflict between this type and rust's String pub hash: Smi, // Smi pub length: Smi, // Smi } #[derive(Default)] pub struct Class<'a> { - pub name: String, // StringPtr -> Raw String - pub user_name: String, // StringPtr -> Raw String - pub functions: Option<&'a mut Array<'a>>, // ArrayPtr - pub functions_hash_table: Option<&'a mut Array<'a>>, // ArrayPtr - pub fields: Option<&'a mut Array<'a>>, // ArrayPtr - pub offset_in_words_to_field: Option<&'a mut Array<'a>>, // ArrayPtr - pub interfaces: Option<&'a mut Array<'a>>, // ArrayPtr - pub script: Option<&'a mut Script<'a>>, // ScriptPtr - pub library: Option<&'a mut Library<'a>>, // LibraryPtr - pub type_parameters: Option<&'a mut TypeParameters<'a>>, // TypeParametersPtr - pub super_type: Option<&'a mut Type<'a>>, // TypePtr - pub constants: Option<&'a mut Array<'a>>, // ArrayPtr - pub declaration_type: Option<&'a mut Type<'a>>, // TypePtr - pub invocation_dispatcher_cache: Option<&'a mut Array<'a>>, // ArrayPtr + pub name: String, // StringPtr -> Raw String + pub user_name: String, // StringPtr -> Raw String + pub functions: Option<&'a mut Array<'a>>, // ArrayPtr + pub functions_hash_table: Option<&'a mut Array<'a>>, // ArrayPtr + pub fields: Option<&'a mut Array<'a>>, // ArrayPtr + pub offset_in_words_to_field: Option<&'a mut Array<'a>>, // ArrayPtr + pub interfaces: Option<&'a mut Array<'a>>, // ArrayPtr + pub script: Option<&'a mut Script<'a>>, // ScriptPtr + pub library: Option<&'a mut Library<'a>>, // LibraryPtr + pub type_parameters: Option<&'a mut TypeParameters<'a>>, // TypeParametersPtr + pub super_type: Option<&'a mut Type<'a>>, // TypePtr + pub constants: Option<&'a mut Array<'a>>, // ArrayPtr + pub declaration_type: Option<&'a mut Type<'a>>, // TypePtr + pub invocation_dispatcher_cache: Option<&'a mut Array<'a>>, // ArrayPtr pub direct_implementors: Option<&'a mut GrowableObjectArray<'a>>, // GrowableObjectArrayPtr pub direct_subclasses: Option<&'a mut GrowableObjectArray<'a>>, // GrowableObjectArrayPtr pub declaration_instance_type_arguments: Option<&'a mut TypeArguments<'a>>, // TypeArgumentsPtr - pub allocation_stub: Option<&'a mut Code<'a>>, // CodePtr - pub dependent_code: Option<&'a mut WeakArray<'a>>, // WeakArrayPtr + pub allocation_stub: Option<&'a mut Code<'a>>, // CodePtr + pub dependent_code: Option<&'a mut WeakArray<'a>>, // WeakArrayPtr pub num_type_arguments: i16, pub num_native_fields: u16, pub state_bits: u32, @@ -164,14 +164,14 @@ pub struct Class<'a> { #[derive(Default)] pub struct Function<'a> { - pub name: String, // StringPtr -> Raw String - pub owner: Option<&'a mut Object<'a>>, // ObjectPtr - pub signature: Option<&'a mut FunctionType<'a>>, // FunctionTypePtr - pub data: Option<&'a mut Object<'a>>, // ObjectPtr + pub name: String, // StringPtr -> Raw String + pub owner: Option<&'a mut Object<'a>>, // ObjectPtr + pub signature: Option<&'a mut FunctionType<'a>>, // FunctionTypePtr + pub data: Option<&'a mut Object<'a>>, // ObjectPtr pub ic_data_array_or_bytecode: Option<&'a mut Object<'a>>, // ObjectPtr - pub code: Option<&'a mut Code<'a>>, // CodePtr + pub code: Option<&'a mut Code<'a>>, // CodePtr pub positional_parameter_names: Option<&'a mut Array<'a>>, // ArrayPtr - pub unoptimized_code: Option<&'a mut Code<'a>>, // CodePtr + pub unoptimized_code: Option<&'a mut Code<'a>>, // CodePtr pub bitmap: u64, pub kernel_offset: i32, pub kind_tag: u32, @@ -179,19 +179,19 @@ pub struct Function<'a> { #[derive(Default)] pub struct Library<'a> { - pub name: String, // StringPtr -> Raw String - pub url: String, // StringPtr -> Raw String - pub private_key: String, // StringPtr -> Raw String - pub dictionary: Option<&'a mut Array<'a>>, // ArrayPtr - pub metadata: Option<&'a mut Array<'a>>, // ArrayPtr - pub toplevel_class: Option<&'a mut Class<'a>>, // ClassPtr + pub name: String, // StringPtr -> Raw String + pub url: String, // StringPtr -> Raw String + pub private_key: String, // StringPtr -> Raw String + pub dictionary: Option<&'a mut Array<'a>>, // ArrayPtr + pub metadata: Option<&'a mut Array<'a>>, // ArrayPtr + pub toplevel_class: Option<&'a mut Class<'a>>, // ClassPtr pub used_scripts: Option<&'a mut GrowableObjectArray<'a>>, // GrowableObjectArrayPtr - pub loading_unit: Option<&'a mut LoadingUnit<'a>>, // LoadingUnitPtr - pub imports: Option<&'a mut Array<'a>>, // ArrayPtr - pub exports: Option<&'a mut Array<'a>>, // ArrayPtr - pub dependencies: Option<&'a mut Array<'a>>, // ArrayPtr + pub loading_unit: Option<&'a mut LoadingUnit<'a>>, // LoadingUnitPtr + pub imports: Option<&'a mut Array<'a>>, // ArrayPtr + pub exports: Option<&'a mut Array<'a>>, // ArrayPtr + pub dependencies: Option<&'a mut Array<'a>>, // ArrayPtr pub kernel_program_info: Option<&'a mut KernelProgramInfo<'a>>, // KernelProgramInfoPtr - pub loaded_scripts: Option<&'a mut Array<'a>>, // ArrayPtr + pub loaded_scripts: Option<&'a mut Array<'a>>, // ArrayPtr pub num_imports: u16, pub load_state: i8, pub flags: u8, @@ -228,17 +228,45 @@ pub struct TwoByteString; // Placeholder structs for references used above that aren't defined yet // it wouldn't compile without this, though for now they remain unimplemented... -pub struct Array<'a> { _marker: std::marker::PhantomData<&'a ()> } -pub struct Object<'a> { _marker: std::marker::PhantomData<&'a ()> } -pub struct AbstractType<'a> { _marker: std::marker::PhantomData<&'a ()> } -pub struct FunctionType<'a> { _marker: std::marker::PhantomData<&'a ()> } -pub struct Script<'a> { _marker: std::marker::PhantomData<&'a ()> } -pub struct Closure<'a> { _marker: std::marker::PhantomData<&'a ()> } -pub struct Instance<'a> { _marker: std::marker::PhantomData<&'a ()> } -pub struct WeakArray<'a> { _marker: std::marker::PhantomData<&'a ()> } -pub struct TypedDataBase<'a> { _marker: std::marker::PhantomData<&'a ()> } -pub struct TypedData<'a> { _marker: std::marker::PhantomData<&'a ()> } -pub struct TypedDataView<'a> { _marker: std::marker::PhantomData<&'a ()> } -pub struct GrowableObjectArray<'a> { _marker: std::marker::PhantomData<&'a ()> } -pub struct Code<'a> { _marker: std::marker::PhantomData<&'a ()> } -pub struct LoadingUnit<'a> { _marker: std::marker::PhantomData<&'a ()> } +pub struct Array<'a> { + _marker: std::marker::PhantomData<&'a ()>, +} +pub struct Object<'a> { + _marker: std::marker::PhantomData<&'a ()>, +} +pub struct AbstractType<'a> { + _marker: std::marker::PhantomData<&'a ()>, +} +pub struct FunctionType<'a> { + _marker: std::marker::PhantomData<&'a ()>, +} +pub struct Script<'a> { + _marker: std::marker::PhantomData<&'a ()>, +} +pub struct Closure<'a> { + _marker: std::marker::PhantomData<&'a ()>, +} +pub struct Instance<'a> { + _marker: std::marker::PhantomData<&'a ()>, +} +pub struct WeakArray<'a> { + _marker: std::marker::PhantomData<&'a ()>, +} +pub struct TypedDataBase<'a> { + _marker: std::marker::PhantomData<&'a ()>, +} +pub struct TypedData<'a> { + _marker: std::marker::PhantomData<&'a ()>, +} +pub struct TypedDataView<'a> { + _marker: std::marker::PhantomData<&'a ()>, +} +pub struct GrowableObjectArray<'a> { + _marker: std::marker::PhantomData<&'a ()>, +} +pub struct Code<'a> { + _marker: std::marker::PhantomData<&'a ()>, +} +pub struct LoadingUnit<'a> { + _marker: std::marker::PhantomData<&'a ()>, +} diff --git a/crates/flutterdec-serwalker/src/snapshot.rs b/crates/flutterdec-serwalker/src/snapshot.rs index f109274..18cdf3a 100644 --- a/crates/flutterdec-serwalker/src/snapshot.rs +++ b/crates/flutterdec-serwalker/src/snapshot.rs @@ -1,16 +1,17 @@ -use crate::cluster::{ Cluster }; -use crate::stream::{ Stream }; +use crate::cluster::Cluster; use crate::constants::{self, MAGIC_BYTES}; +use crate::stream::Stream; -enum SnapshotKind // pulled straight out of the C++ def +enum SnapshotKind +// pulled straight out of the C++ def { - Full, - FullCore, - FullJIT, - FullAOT, // Full + AOT code, this is the one we care about, as th - Module, - None, - Invalid + Full, + FullCore, + FullJIT, + FullAOT, // Full + AOT code, this is the one we care about, as th + Module, + None, + Invalid, } impl TryFrom for SnapshotKind { @@ -29,8 +30,7 @@ impl TryFrom for SnapshotKind { } } } -struct DataSnapshot -{ +struct DataSnapshot { // this array will contain mutable references to all clusters, and it will be indexed using the class id clusters: [Box; constants::MAX_CLUSTER_NUM], // thus each cluster must have its own UNIQUE class id @@ -53,12 +53,10 @@ struct DataSnapshot } impl DataSnapshot { - fn parse_header(&mut self, stream: &mut Stream) - { + fn parse_header(&mut self, stream: &mut Stream) { self.magic_bytes = stream.read_u32(); - if self.magic_bytes != MAGIC_BYTES - { + if self.magic_bytes != MAGIC_BYTES { panic!("Not a snapshot...") } @@ -68,17 +66,14 @@ impl DataSnapshot { self.parse_version_and_features(stream); } - fn parse_version_and_features(&mut self, stream: &mut Stream) - { + fn parse_version_and_features(&mut self, stream: &mut Stream) { let mut version_and_features = stream.read_c_string(); self.features = version_and_features.split_off(constants::VERSION_HASH_LENGTH); // returns (str[hash_len..]) self.version_hash = version_and_features; } - fn read_clusters(&mut self, stream: &Stream) - { + fn read_clusters(&mut self, stream: &Stream) { let curr_ref_id: u64 = 0; // all objects are numbered starting from 0 - } -} \ No newline at end of file +} diff --git a/crates/flutterdec-serwalker/src/stream.rs b/crates/flutterdec-serwalker/src/stream.rs index 2a9c45d..a0c52e4 100644 --- a/crates/flutterdec-serwalker/src/stream.rs +++ b/crates/flutterdec-serwalker/src/stream.rs @@ -1,40 +1,42 @@ -use crate::constants::{UNSIGNED_END_OF_DATA_BYTE, SIGNED_END_OF_DATA_BYTE, UNSIGNED_MAX_DATA_PER_BYTE, DATA_BITS_PER_BYTE}; -pub struct Stream<'a> -{ +use crate::constants::{ + DATA_BITS_PER_BYTE, SIGNED_END_OF_DATA_BYTE, UNSIGNED_END_OF_DATA_BYTE, + UNSIGNED_MAX_DATA_PER_BYTE, +}; +pub struct Stream<'a> { byte_stream: &'a [u8], curr_stream_offset: usize, } -impl<'a> Stream<'a> -{ +impl<'a> Stream<'a> { fn seek(&mut self, pos: usize) // might be useful? { - if self.byte_stream.len() > pos && pos > 0 { self.curr_stream_offset = pos; } + if self.byte_stream.len() > pos && pos > 0 { + self.curr_stream_offset = pos; + } } - pub fn advance_pos(&mut self, num_bytes: usize) - { + pub fn advance_pos(&mut self, num_bytes: usize) { self.curr_stream_offset += num_bytes; } - pub fn get_current_pos(&self) -> usize - { + pub fn get_current_pos(&self) -> usize { self.curr_stream_offset } /* - Reads a modified uleb from the current stream offset. + Reads a modified uleb from the current stream offset. - Dart uses a modified LEB128 format. The normal format uses bytes with their MSb set in order - to signify that there are more bytes ahead, and the last byte has its MSb unset, whereas Dart's - implementation does the opposite. The "continuation" bit on each byte is 0, and the last byte - has its MSb set. - */ + Dart uses a modified LEB128 format. The normal format uses bytes with their MSb set in order + to signify that there are more bytes ahead, and the last byte has its MSb unset, whereas Dart's + implementation does the opposite. The "continuation" bit on each byte is 0, and the last byte + has its MSb set. + */ pub fn read_modified_leb128(&mut self, sign_marker: u8) -> u64 // 8 bytes should be enough for anything... { let mut idx: u8 = 0; - let first_byte = self.byte_stream[self.curr_stream_offset]; - if first_byte > UNSIGNED_MAX_DATA_PER_BYTE // if the first byte has its MSb set + let first_byte = self.byte_stream[self.curr_stream_offset]; + if first_byte > UNSIGNED_MAX_DATA_PER_BYTE + // if the first byte has its MSb set { self.advance_pos(1); // wrapping_sub mimics C++ unsigned underflow, giving us perfect sign-extension @@ -47,7 +49,9 @@ impl<'a> Stream<'a> loop { byte = self.byte_stream[self.curr_stream_offset + idx as usize]; - if byte & UNSIGNED_END_OF_DATA_BYTE == UNSIGNED_END_OF_DATA_BYTE { break; } // final byte + if byte & UNSIGNED_END_OF_DATA_BYTE == UNSIGNED_END_OF_DATA_BYTE { + break; + } // final byte read_num |= (byte as u64) << (idx as usize * DATA_BITS_PER_BYTE); idx += 1; } @@ -61,28 +65,24 @@ impl<'a> Stream<'a> read_num } - pub fn read_u64(&mut self) -> u64 - { + pub fn read_u64(&mut self) -> u64 { let u64_size = std::mem::size_of::(); - let num_slice = &self.byte_stream[self.curr_stream_offset..self.curr_stream_offset + u64_size]; + let num_slice = + &self.byte_stream[self.curr_stream_offset..self.curr_stream_offset + u64_size]; - let converted_slice: [u8; 8] = num_slice - .try_into() - .expect("Slice wasn't 8 bytes long..."); + let converted_slice: [u8; 8] = num_slice.try_into().expect("Slice wasn't 8 bytes long..."); self.advance_pos(u64_size); u64::from_le_bytes(converted_slice) } - pub fn read_u32(&mut self) -> u32 - { + pub fn read_u32(&mut self) -> u32 { let u32_size = std::mem::size_of::(); - let num_slice = &self.byte_stream[self.curr_stream_offset..self.curr_stream_offset + u32_size]; + let num_slice = + &self.byte_stream[self.curr_stream_offset..self.curr_stream_offset + u32_size]; - let converted_slice: [u8; 4] = num_slice - .try_into() - .expect("Slice wasn't 4 bytes long..."); + let converted_slice: [u8; 4] = num_slice.try_into().expect("Slice wasn't 4 bytes long..."); self.advance_pos(u32_size); @@ -90,22 +90,22 @@ impl<'a> Stream<'a> } /* - Panics if it isn't possible to create a stream from the utf-8 representation stored in - the byte slice. It shouldn't happen, so the best possible outcome is to assume some - logic mistake has been made and end the application. It should be a good thing to change this to an - unwrap_or_else so that we can also print the stream offset and cluster where this error occurred, in order - to have static debug info. - */ - pub fn read_c_string(&mut self) -> String - { - let first_nullbyte_pos = self.byte_stream - .iter() - .position(|&b| b == 0x00) - .unwrap_or(self.byte_stream.len()); + Panics if it isn't possible to create a stream from the utf-8 representation stored in + the byte slice. It shouldn't happen, so the best possible outcome is to assume some + logic mistake has been made and end the application. It should be a good thing to change this to an + unwrap_or_else so that we can also print the stream offset and cluster where this error occurred, in order + to have static debug info. + */ + pub fn read_c_string(&mut self) -> String { + let first_nullbyte_pos = self + .byte_stream + .iter() + .position(|&b| b == 0x00) + .unwrap_or(self.byte_stream.len()); let raw_str = &self.byte_stream[..=first_nullbyte_pos]; self.advance_pos(raw_str.len()); String::from_utf8(raw_str.to_vec()).unwrap() // it should be horrible if for some reason a string just isn't there } -} \ No newline at end of file +} diff --git a/crates/flutterdec-serwalker/src/utils.rs b/crates/flutterdec-serwalker/src/utils.rs index 049d66f..4f7eb30 100644 --- a/crates/flutterdec-serwalker/src/utils.rs +++ b/crates/flutterdec-serwalker/src/utils.rs @@ -1,11 +1,10 @@ -use paste::paste; use crate::cluster::Cluster; -use crate::stream::Stream; use crate::constants::UNSIGNED_M; +use crate::stream::Stream; +use paste::paste; #[macro_export] -macro_rules! DECLARE_FIXED_LENGTH_CLUSTER -{ +macro_rules! DECLARE_FIXED_LENGTH_CLUSTER { ($name:ident, $fill_impl:block) => { ::paste::paste! { // this is ugly, but the language doesn't support identifier concatenation struct [<$name Cluster>] @@ -22,7 +21,7 @@ macro_rules! DECLARE_FIXED_LENGTH_CLUSTER objs: Vec<(u64, Box<$name>)> // a pair (ref_id, object) } - impl Cluster for [<$name Cluster>] + impl Cluster for [<$name Cluster>] { fn read_alloc(&mut self, last_ref_id: &mut u64, stream: &mut Stream) -> usize // read tags and count { @@ -51,4 +50,4 @@ macro_rules! DECLARE_FIXED_LENGTH_CLUSTER } } }; -} \ No newline at end of file +} From 3436a5105653be31a6a8977ab8fb58f2a196df70 Mon Sep 17 00:00:00 2001 From: kalixtez Date: Mon, 27 Apr 2026 15:40:02 -0500 Subject: [PATCH 17/50] fixed read_c_string --- crates/flutterdec-serwalker/src/cluster/mod.rs | 2 +- crates/flutterdec-serwalker/src/stream.rs | 13 +++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/crates/flutterdec-serwalker/src/cluster/mod.rs b/crates/flutterdec-serwalker/src/cluster/mod.rs index c1559ca..2003d67 100644 --- a/crates/flutterdec-serwalker/src/cluster/mod.rs +++ b/crates/flutterdec-serwalker/src/cluster/mod.rs @@ -20,7 +20,7 @@ pub fn read_cluster_fill() { } pub fn read_and_decompress_smi(stream: &mut Stream) -> Smi { - let raw_smi = stream.read_modified_leb128(UNSIGNED_M); // smis are always written as signed + let raw_smi = stream.read_modified_leb128(SIGNED_M); // smis are always written as signed (raw_smi as Smi) >> constants::SMI_SHIFT } diff --git a/crates/flutterdec-serwalker/src/stream.rs b/crates/flutterdec-serwalker/src/stream.rs index a0c52e4..db024a5 100644 --- a/crates/flutterdec-serwalker/src/stream.rs +++ b/crates/flutterdec-serwalker/src/stream.rs @@ -10,7 +10,7 @@ pub struct Stream<'a> { impl<'a> Stream<'a> { fn seek(&mut self, pos: usize) // might be useful? { - if self.byte_stream.len() > pos && pos > 0 { + if self.byte_stream.len() > pos && pos >= 0 { self.curr_stream_offset = pos; } } @@ -97,14 +97,15 @@ impl<'a> Stream<'a> { to have static debug info. */ pub fn read_c_string(&mut self) -> String { - let first_nullbyte_pos = self - .byte_stream + + let first_nullbyte_pos = + self.byte_stream[self.curr_stream_offset..] .iter() .position(|&b| b == 0x00) - .unwrap_or(self.byte_stream.len()); + .expect("Reading a string until the end of the stream? Something definitely went wrong..."); - let raw_str = &self.byte_stream[..=first_nullbyte_pos]; - self.advance_pos(raw_str.len()); + let raw_str = &self.byte_stream[self.curr_stream_offset..self.curr_stream_offset + first_nullbyte_pos]; + self.advance_pos(raw_str.len() + 1); String::from_utf8(raw_str.to_vec()).unwrap() // it should be horrible if for some reason a string just isn't there } From d84b948181cf93027bb93967b50e8dbf5ec7021f Mon Sep 17 00:00:00 2001 From: kalixtez Date: Mon, 27 Apr 2026 15:44:07 -0500 Subject: [PATCH 18/50] fixed read_and_decompress_smi --- crates/flutterdec-serwalker/src/cluster/mod.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/crates/flutterdec-serwalker/src/cluster/mod.rs b/crates/flutterdec-serwalker/src/cluster/mod.rs index c5319d3..2003d67 100644 --- a/crates/flutterdec-serwalker/src/cluster/mod.rs +++ b/crates/flutterdec-serwalker/src/cluster/mod.rs @@ -20,11 +20,7 @@ pub fn read_cluster_fill() { } pub fn read_and_decompress_smi(stream: &mut Stream) -> Smi { -<<<<<<< HEAD let raw_smi = stream.read_modified_leb128(SIGNED_M); // smis are always written as signed -======= - let raw_smi = stream.read_modified_leb128(UNSIGNED_M); // smis are always written as signed ->>>>>>> b36733ae4d7105ce3e276c11bb3a44f57b3cccff (raw_smi as Smi) >> constants::SMI_SHIFT } From 399e3324938993aadd625cefca29469bb1611513 Mon Sep 17 00:00:00 2001 From: kalixtez Date: Wed, 29 Apr 2026 23:31:34 -0500 Subject: [PATCH 19/50] feature(serwalker): added implementations to read non null-terminated strings and ref ids, fixed minor bugs --- .../flutterdec-serwalker/src/cluster/mod.rs | 6 +- .../src/raw_object/mod.rs | 25 ++++---- crates/flutterdec-serwalker/src/snapshot.rs | 59 ++++++++++++++++--- crates/flutterdec-serwalker/src/stream.rs | 51 +++++++++++++++- 4 files changed, 112 insertions(+), 29 deletions(-) diff --git a/crates/flutterdec-serwalker/src/cluster/mod.rs b/crates/flutterdec-serwalker/src/cluster/mod.rs index 2003d67..2781c22 100644 --- a/crates/flutterdec-serwalker/src/cluster/mod.rs +++ b/crates/flutterdec-serwalker/src/cluster/mod.rs @@ -22,7 +22,7 @@ pub fn read_cluster_fill() { pub fn read_and_decompress_smi(stream: &mut Stream) -> Smi { let raw_smi = stream.read_modified_leb128(SIGNED_M); // smis are always written as signed - (raw_smi as Smi) >> constants::SMI_SHIFT + raw_smi as Smi } pub fn decide_cluster( @@ -39,13 +39,11 @@ pub fn decide_cluster( // whose fill cluster size is uniquely determined by sizeof(Object) * num_of_objects // and alloc cluster size is tags (MULEB128) + num_of_objects (MULEB128) -DECLARE_FIXED_LENGTH_CLUSTER!(OneByteString, { +DECLARE_FIXED_LENGTH_CLUSTER!(_String, { 1 // to-do }); /* -DECLARE_FIXED_LENGTH_CLUSTER!(TwoByteString, 8); -DECLARE_FIXED_LENGTH_CLUSTER!(String, 8); DECLARE_FIXED_LENGTH_CLUSTER!(Mint, 16); DECLARE_FIXED_LENGTH_CLUSTER!(Double, 16); DECLARE_FIXED_LENGTH_CLUSTER!(TypeParameter, 32); diff --git a/crates/flutterdec-serwalker/src/raw_object/mod.rs b/crates/flutterdec-serwalker/src/raw_object/mod.rs index 3692170..e26c820 100644 --- a/crates/flutterdec-serwalker/src/raw_object/mod.rs +++ b/crates/flutterdec-serwalker/src/raw_object/mod.rs @@ -67,7 +67,7 @@ pub struct FfiTrampolineData<'a> { #[derive(Default)] pub struct Field<'a> { - pub name: String, // StringPtr -> Raw String + pub name: _String, // _StringPtr -> Raw _String pub owner: Option<&'a mut Object<'a>>, // ObjectPtr pub type_field: Option<&'a mut AbstractType<'a>>, // AbstractTypePtr pub initializer_function: Option<&'a mut Function<'a>>, // FunctionPtr @@ -123,16 +123,17 @@ pub struct UnlinkedCall { } #[derive(Default)] -pub struct String_ { - // added underscore so there's no conflict between this type and rust's String +pub struct _String { + // added underscore so there's no conflict between this type and rust's _String pub hash: Smi, // Smi pub length: Smi, // Smi + pub inner_string: String } #[derive(Default)] pub struct Class<'a> { - pub name: String, // StringPtr -> Raw String - pub user_name: String, // StringPtr -> Raw String + pub name: _String, // _StringPtr -> Raw _String + pub user_name: _String, // _StringPtr -> Raw _String pub functions: Option<&'a mut Array<'a>>, // ArrayPtr pub functions_hash_table: Option<&'a mut Array<'a>>, // ArrayPtr pub fields: Option<&'a mut Array<'a>>, // ArrayPtr @@ -164,7 +165,7 @@ pub struct Class<'a> { #[derive(Default)] pub struct Function<'a> { - pub name: String, // StringPtr -> Raw String + pub name: _String, // _StringPtr -> Raw _String pub owner: Option<&'a mut Object<'a>>, // ObjectPtr pub signature: Option<&'a mut FunctionType<'a>>, // FunctionTypePtr pub data: Option<&'a mut Object<'a>>, // ObjectPtr @@ -179,9 +180,9 @@ pub struct Function<'a> { #[derive(Default)] pub struct Library<'a> { - pub name: String, // StringPtr -> Raw String - pub url: String, // StringPtr -> Raw String - pub private_key: String, // StringPtr -> Raw String + pub name: _String, // _StringPtr -> Raw _String + pub url: _String, // _StringPtr -> Raw _String + pub private_key: _String, // _StringPtr -> Raw _String pub dictionary: Option<&'a mut Array<'a>>, // ArrayPtr pub metadata: Option<&'a mut Array<'a>>, // ArrayPtr pub toplevel_class: Option<&'a mut Class<'a>>, // ClassPtr @@ -220,12 +221,6 @@ pub struct PcDescriptors; #[derive(Default)] pub struct ObjectPool; -#[derive(Default)] -pub struct OneByteString; - -#[derive(Default)] -pub struct TwoByteString; - // Placeholder structs for references used above that aren't defined yet // it wouldn't compile without this, though for now they remain unimplemented... pub struct Array<'a> { diff --git a/crates/flutterdec-serwalker/src/snapshot.rs b/crates/flutterdec-serwalker/src/snapshot.rs index 18cdf3a..61c28fb 100644 --- a/crates/flutterdec-serwalker/src/snapshot.rs +++ b/crates/flutterdec-serwalker/src/snapshot.rs @@ -1,7 +1,8 @@ use crate::cluster::Cluster; -use crate::constants::{self, MAGIC_BYTES}; +use crate::constants::{self, MAGIC_BYTES, UNSIGNED_M}; use crate::stream::Stream; +#[derive(Default)] enum SnapshotKind // pulled straight out of the C++ def { @@ -10,6 +11,7 @@ enum SnapshotKind FullJIT, FullAOT, // Full + AOT code, this is the one we care about, as th Module, + #[default] None, Invalid, } @@ -30,9 +32,10 @@ impl TryFrom for SnapshotKind { } } } + struct DataSnapshot { // this array will contain mutable references to all clusters, and it will be indexed using the class id - clusters: [Box; constants::MAX_CLUSTER_NUM], // thus each cluster must have its own UNIQUE class id + clusters: [Option>; constants::MAX_CLUSTER_NUM], // thus each cluster must have its own UNIQUE class id magic_bytes: u32, size: u64, @@ -45,14 +48,43 @@ struct DataSnapshot { num_objects: u64, num_clusters: u64, - instr_table_len: u64, + instr_table_len: usize, instr_table_offset: usize, start_of_alloc_area: usize, start_of_fill_area: usize, } +impl Default for DataSnapshot { + fn default() -> Self { + const INIT_CLUSTER: Option> = None; + Self { + clusters: [INIT_CLUSTER; constants::MAX_CLUSTER_NUM], + magic_bytes: 0, + size: 0, + kind: SnapshotKind::default(), + version_hash: String::new(), + features: String::new(), + num_base_objects: 0, + num_objects: 0, + num_clusters: 0, + instr_table_len: 0, + instr_table_offset: 0, + start_of_alloc_area: 0, + start_of_fill_area: 0, + } + } +} + impl DataSnapshot { + + fn parse_version_and_features(&mut self, stream: &mut Stream) { + let mut version_and_features = stream.read_c_string(); + + self.features = version_and_features.split_off(constants::VERSION_HASH_LENGTH); // returns (str[hash_len..]) + self.version_hash = version_and_features; + } + fn parse_header(&mut self, stream: &mut Stream) { self.magic_bytes = stream.read_u32(); @@ -64,16 +96,25 @@ impl DataSnapshot { self.kind = SnapshotKind::try_from(stream.read_u64()).expect("Not a valid snapshot!"); self.parse_version_and_features(stream); - } - fn parse_version_and_features(&mut self, stream: &mut Stream) { - let mut version_and_features = stream.read_c_string(); + self.num_base_objects = stream.read_modified_leb128(UNSIGNED_M); + self.num_objects = stream.read_modified_leb128(UNSIGNED_M); + self.num_clusters = stream.read_modified_leb128(UNSIGNED_M); - self.features = version_and_features.split_off(constants::VERSION_HASH_LENGTH); // returns (str[hash_len..]) - self.version_hash = version_and_features; + self.instr_table_len = stream.read_modified_leb128(UNSIGNED_M) as usize; + self.instr_table_offset = stream.read_modified_leb128(UNSIGNED_M) as usize; + + self.start_of_alloc_area = stream.get_current_pos(); } - fn read_clusters(&mut self, stream: &Stream) { + fn read_clusters(&mut self, stream: &mut Stream) { let curr_ref_id: u64 = 0; // all objects are numbered starting from 0 } + + pub fn parse_snapshot(&mut self, stream: &mut Stream) + { + println!("Now parsing the snapshot..."); + self.parse_header(stream); + } + } diff --git a/crates/flutterdec-serwalker/src/stream.rs b/crates/flutterdec-serwalker/src/stream.rs index db024a5..bda9215 100644 --- a/crates/flutterdec-serwalker/src/stream.rs +++ b/crates/flutterdec-serwalker/src/stream.rs @@ -107,6 +107,55 @@ impl<'a> Stream<'a> { let raw_str = &self.byte_stream[self.curr_stream_offset..self.curr_stream_offset + first_nullbyte_pos]; self.advance_pos(raw_str.len() + 1); - String::from_utf8(raw_str.to_vec()).unwrap() // it should be horrible if for some reason a string just isn't there + String::from_utf8(raw_str.to_vec()).expect("Couldn't turn null-terminated UTF-8 bytes into a String.") // it should be horrible if for some reason a string just isn't there + } + + // read a non null-terminated string given a length + pub fn read_string(&mut self, len: usize) -> String + { + let final_pos = self.curr_stream_offset + len; + let raw_str = &self.byte_stream[self.curr_stream_offset..final_pos]; + + self.advance_pos(len); + + String::from_utf8(raw_str.to_vec()).expect("Couldn't turn UTF-8 bytes into a String.") + } + + /* + Complex object types (i.e, object types that contain other object types) + point to other objects through refids, which is essentially the core + mechanism of Dart's serialization/deserialization process, allowing the + reconstruction of all objects from the snapshot into the heap. + */ + pub fn read_ref_id(&mut self) -> u32 + { + let mut idx: usize = 0; + let mut byte: i8 = self.byte_stream[self.curr_stream_offset + idx] as i8; + let mut ref_id: i32 = 0; // as far as I know, ref_ids are up to 2^28, so 32 bits is good enough + + if byte < 0 + { + ref_id += byte as i32; + self.advance_pos(1); + return (ref_id + 128) as u32; + } + + loop + { + ref_id = ref_id << 7; + ref_id += byte as i32; + idx += 1; + + if byte < 0 + { + break + } + + byte = self.byte_stream[self.curr_stream_offset + idx] as i8; + } + + self.advance_pos(idx); + + (ref_id + 128) as u32 } } From a7623275a8c76613e6442d7e173344bdecdce51c Mon Sep 17 00:00:00 2001 From: kalixtez Date: Wed, 29 Apr 2026 23:38:32 -0500 Subject: [PATCH 20/50] refactor(serwalker): ran cargo fmt --all --- .../src/raw_object/mod.rs | 42 +++++++++---------- crates/flutterdec-serwalker/src/snapshot.rs | 7 +--- crates/flutterdec-serwalker/src/stream.rs | 35 +++++++--------- 3 files changed, 39 insertions(+), 45 deletions(-) diff --git a/crates/flutterdec-serwalker/src/raw_object/mod.rs b/crates/flutterdec-serwalker/src/raw_object/mod.rs index e26c820..655e79e 100644 --- a/crates/flutterdec-serwalker/src/raw_object/mod.rs +++ b/crates/flutterdec-serwalker/src/raw_object/mod.rs @@ -67,7 +67,7 @@ pub struct FfiTrampolineData<'a> { #[derive(Default)] pub struct Field<'a> { - pub name: _String, // _StringPtr -> Raw _String + pub name: _String, // StringPtr -> Raw String pub owner: Option<&'a mut Object<'a>>, // ObjectPtr pub type_field: Option<&'a mut AbstractType<'a>>, // AbstractTypePtr pub initializer_function: Option<&'a mut Function<'a>>, // FunctionPtr @@ -127,13 +127,13 @@ pub struct _String { // added underscore so there's no conflict between this type and rust's _String pub hash: Smi, // Smi pub length: Smi, // Smi - pub inner_string: String + pub inner_string: String, } #[derive(Default)] pub struct Class<'a> { - pub name: _String, // _StringPtr -> Raw _String - pub user_name: _String, // _StringPtr -> Raw _String + pub name: _String, // StringPtr -> Raw String + pub user_name: _String, // StringPtr -> Raw String pub functions: Option<&'a mut Array<'a>>, // ArrayPtr pub functions_hash_table: Option<&'a mut Array<'a>>, // ArrayPtr pub fields: Option<&'a mut Array<'a>>, // ArrayPtr @@ -165,14 +165,14 @@ pub struct Class<'a> { #[derive(Default)] pub struct Function<'a> { - pub name: _String, // _StringPtr -> Raw _String - pub owner: Option<&'a mut Object<'a>>, // ObjectPtr - pub signature: Option<&'a mut FunctionType<'a>>, // FunctionTypePtr - pub data: Option<&'a mut Object<'a>>, // ObjectPtr + pub name: _String, // StringPtr -> Raw String + pub owner: Option<&'a mut Object<'a>>, // ObjectPtr + pub signature: Option<&'a mut FunctionType<'a>>, // FunctionTypePtr + pub data: Option<&'a mut Object<'a>>, // ObjectPtr pub ic_data_array_or_bytecode: Option<&'a mut Object<'a>>, // ObjectPtr - pub code: Option<&'a mut Code<'a>>, // CodePtr + pub code: Option<&'a mut Code<'a>>, // CodePtr pub positional_parameter_names: Option<&'a mut Array<'a>>, // ArrayPtr - pub unoptimized_code: Option<&'a mut Code<'a>>, // CodePtr + pub unoptimized_code: Option<&'a mut Code<'a>>, // CodePtr pub bitmap: u64, pub kernel_offset: i32, pub kind_tag: u32, @@ -180,19 +180,19 @@ pub struct Function<'a> { #[derive(Default)] pub struct Library<'a> { - pub name: _String, // _StringPtr -> Raw _String - pub url: _String, // _StringPtr -> Raw _String - pub private_key: _String, // _StringPtr -> Raw _String - pub dictionary: Option<&'a mut Array<'a>>, // ArrayPtr - pub metadata: Option<&'a mut Array<'a>>, // ArrayPtr - pub toplevel_class: Option<&'a mut Class<'a>>, // ClassPtr + pub name: _String, // StringPtr -> Raw String + pub url: _String, // StringPtr -> Raw String + pub private_key: _String, // StringPtr -> Raw String + pub dictionary: Option<&'a mut Array<'a>>, // ArrayPtr + pub metadata: Option<&'a mut Array<'a>>, // ArrayPtr + pub toplevel_class: Option<&'a mut Class<'a>>, // ClassPtr pub used_scripts: Option<&'a mut GrowableObjectArray<'a>>, // GrowableObjectArrayPtr - pub loading_unit: Option<&'a mut LoadingUnit<'a>>, // LoadingUnitPtr - pub imports: Option<&'a mut Array<'a>>, // ArrayPtr - pub exports: Option<&'a mut Array<'a>>, // ArrayPtr - pub dependencies: Option<&'a mut Array<'a>>, // ArrayPtr + pub loading_unit: Option<&'a mut LoadingUnit<'a>>, // LoadingUnitPtr + pub imports: Option<&'a mut Array<'a>>, // ArrayPtr + pub exports: Option<&'a mut Array<'a>>, // ArrayPtr + pub dependencies: Option<&'a mut Array<'a>>, // ArrayPtr pub kernel_program_info: Option<&'a mut KernelProgramInfo<'a>>, // KernelProgramInfoPtr - pub loaded_scripts: Option<&'a mut Array<'a>>, // ArrayPtr + pub loaded_scripts: Option<&'a mut Array<'a>>, // ArrayPtr pub num_imports: u16, pub load_state: i8, pub flags: u8, diff --git a/crates/flutterdec-serwalker/src/snapshot.rs b/crates/flutterdec-serwalker/src/snapshot.rs index 61c28fb..b7d7cb5 100644 --- a/crates/flutterdec-serwalker/src/snapshot.rs +++ b/crates/flutterdec-serwalker/src/snapshot.rs @@ -77,7 +77,6 @@ impl Default for DataSnapshot { } impl DataSnapshot { - fn parse_version_and_features(&mut self, stream: &mut Stream) { let mut version_and_features = stream.read_c_string(); @@ -107,14 +106,12 @@ impl DataSnapshot { self.start_of_alloc_area = stream.get_current_pos(); } - fn read_clusters(&mut self, stream: &mut Stream) { + fn parse_clusters(&mut self, stream: &mut Stream) { let curr_ref_id: u64 = 0; // all objects are numbered starting from 0 } - pub fn parse_snapshot(&mut self, stream: &mut Stream) - { + pub fn parse_snapshot(&mut self, stream: &mut Stream) { println!("Now parsing the snapshot..."); self.parse_header(stream); } - } diff --git a/crates/flutterdec-serwalker/src/stream.rs b/crates/flutterdec-serwalker/src/stream.rs index bda9215..06c2aed 100644 --- a/crates/flutterdec-serwalker/src/stream.rs +++ b/crates/flutterdec-serwalker/src/stream.rs @@ -97,22 +97,23 @@ impl<'a> Stream<'a> { to have static debug info. */ pub fn read_c_string(&mut self) -> String { - - let first_nullbyte_pos = - self.byte_stream[self.curr_stream_offset..] + let first_nullbyte_pos = self.byte_stream[self.curr_stream_offset..] .iter() .position(|&b| b == 0x00) - .expect("Reading a string until the end of the stream? Something definitely went wrong..."); + .expect( + "Reading a string until the end of the stream? Something definitely went wrong...", + ); - let raw_str = &self.byte_stream[self.curr_stream_offset..self.curr_stream_offset + first_nullbyte_pos]; + let raw_str = &self.byte_stream + [self.curr_stream_offset..self.curr_stream_offset + first_nullbyte_pos]; self.advance_pos(raw_str.len() + 1); - String::from_utf8(raw_str.to_vec()).expect("Couldn't turn null-terminated UTF-8 bytes into a String.") // it should be horrible if for some reason a string just isn't there + String::from_utf8(raw_str.to_vec()) + .expect("Couldn't turn null-terminated UTF-8 bytes into a String.") // it should be horrible if for some reason a string just isn't there } // read a non null-terminated string given a length - pub fn read_string(&mut self, len: usize) -> String - { + pub fn read_string(&mut self, len: usize) -> String { let final_pos = self.curr_stream_offset + len; let raw_str = &self.byte_stream[self.curr_stream_offset..final_pos]; @@ -121,34 +122,30 @@ impl<'a> Stream<'a> { String::from_utf8(raw_str.to_vec()).expect("Couldn't turn UTF-8 bytes into a String.") } - /* + /* Complex object types (i.e, object types that contain other object types) point to other objects through refids, which is essentially the core mechanism of Dart's serialization/deserialization process, allowing the reconstruction of all objects from the snapshot into the heap. */ - pub fn read_ref_id(&mut self) -> u32 - { + pub fn read_ref_id(&mut self) -> u32 { let mut idx: usize = 0; let mut byte: i8 = self.byte_stream[self.curr_stream_offset + idx] as i8; let mut ref_id: i32 = 0; // as far as I know, ref_ids are up to 2^28, so 32 bits is good enough - if byte < 0 - { + if byte < 0 { ref_id += byte as i32; self.advance_pos(1); return (ref_id + 128) as u32; } - - loop - { + + loop { ref_id = ref_id << 7; ref_id += byte as i32; idx += 1; - if byte < 0 - { - break + if byte < 0 { + break; } byte = self.byte_stream[self.curr_stream_offset + idx] as i8; From e7faa9bd7e164d059d168c192e6af0325033ba1b Mon Sep 17 00:00:00 2001 From: kalixtez Date: Sat, 2 May 2026 02:36:26 -0500 Subject: [PATCH 21/50] feat(serwalker): implemented tag-decoding macros, declared all fixed-size alloc cluster types, implemented parse_clusters, implemented a macro to declare the ClassId enum and TryFrom for u32 for said enum --- .../flutterdec-serwalker/src/cluster/mod.rs | 179 +++++++- crates/flutterdec-serwalker/src/constants.rs | 400 ++++++++++-------- .../src/raw_object/mod.rs | 2 +- crates/flutterdec-serwalker/src/snapshot.rs | 70 +-- crates/flutterdec-serwalker/src/stream.rs | 7 +- crates/flutterdec-serwalker/src/utils.rs | 61 ++- 6 files changed, 485 insertions(+), 234 deletions(-) diff --git a/crates/flutterdec-serwalker/src/cluster/mod.rs b/crates/flutterdec-serwalker/src/cluster/mod.rs index 2781c22..8bb1927 100644 --- a/crates/flutterdec-serwalker/src/cluster/mod.rs +++ b/crates/flutterdec-serwalker/src/cluster/mod.rs @@ -1,7 +1,8 @@ use crate::constants::{ClassId, ClassId::*, SIGNED_M, UNSIGNED_M}; use crate::raw_object::*; +use crate::stream::Stream; use crate::DECLARE_FIXED_LENGTH_CLUSTER; -use crate::{constants, stream::Stream}; +use crate::FFI_TYPES_LIST; type Smi = i32; @@ -12,25 +13,30 @@ pub trait Cluster { } pub fn read_cluster_alloc() { - let curr_ref_id: u64 = 0; + //let curr_ref_id: u64 = 0; } pub fn read_cluster_fill() { - let curr_ref_id: u64 = 0; + //let curr_ref_id: u64 = 0; } -pub fn read_and_decompress_smi(stream: &mut Stream) -> Smi { - let raw_smi = stream.read_modified_leb128(SIGNED_M); // smis are always written as signed +pub fn read_smi(stream: &mut Stream) -> Smi { + let raw_smi = stream.read_modified_leb128(SIGNED_M); // smis are always written as signed numbers raw_smi as Smi } -pub fn decide_cluster( - clusters: &mut [Box; constants::MAX_CLUSTER_NUM], - class_id: ClassId, -) -> Result, &str> { +macro_rules! FFI_CASE_PATTERN { + ( $( $ffi_type:ident ),* ) => { + $( $ffi_type )|* + }; +} + +pub fn decide_cluster(class_id: ClassId) -> Result, &'static str> { match class_id { + // we assume compressed pointers, it supports only Android for now... IllegalCid => Err("Not a supported class (illegal class)..."), + FFI_TYPES_LIST!(FFI_CASE_PATTERN) => Err("To do..."), _ => Err("Not a supported class..."), } } @@ -39,14 +45,153 @@ pub fn decide_cluster( // whose fill cluster size is uniquely determined by sizeof(Object) * num_of_objects // and alloc cluster size is tags (MULEB128) + num_of_objects (MULEB128) -DECLARE_FIXED_LENGTH_CLUSTER!(_String, { +DECLARE_FIXED_LENGTH_CLUSTER!(TypeParameters, { + 1 + // to-do +}); + +DECLARE_FIXED_LENGTH_CLUSTER!(PatchClass, { + 1 + // to-do +}); + +DECLARE_FIXED_LENGTH_CLUSTER!(Function, { + 1 + // to-do +}); + +DECLARE_FIXED_LENGTH_CLUSTER!(ClosureData, { + 1 + // to-do +}); +DECLARE_FIXED_LENGTH_CLUSTER!(FfiTrampolineData, { + 1 + // to-do +}); + +DECLARE_FIXED_LENGTH_CLUSTER!(Field, { + 1 + // to-do +}); +DECLARE_FIXED_LENGTH_CLUSTER!(Script, { + 1 + // to-do +}); + +DECLARE_FIXED_LENGTH_CLUSTER!(Library, { + 1 + // to-do +}); +DECLARE_FIXED_LENGTH_CLUSTER!(Namespace, { + 1 + // to-do +}); + +DECLARE_FIXED_LENGTH_CLUSTER!(KernelProgramInfo, { + 1 + // to-do +}); +DECLARE_FIXED_LENGTH_CLUSTER!(UnlinkedCall, { + 1 + // to-do +}); + +DECLARE_FIXED_LENGTH_CLUSTER!(ICData, { + 1 + // to-do +}); +DECLARE_FIXED_LENGTH_CLUSTER!(MegamorphicCache, { + 1 + // to-do +}); + +DECLARE_FIXED_LENGTH_CLUSTER!(SubtypeTestCache, { + 1 + // to-do +}); +DECLARE_FIXED_LENGTH_CLUSTER!(LoadingUnit, { + 1 + // to-do +}); + +DECLARE_FIXED_LENGTH_CLUSTER!(LanguageError, { + 1 + // to-do +}); +DECLARE_FIXED_LENGTH_CLUSTER!(UnhandledException, { + 1 + // to-do +}); + +DECLARE_FIXED_LENGTH_CLUSTER!(LibraryPrefix, { + 1 + // to-do +}); +DECLARE_FIXED_LENGTH_CLUSTER!(Type, { + 1 + // to-do +}); + +DECLARE_FIXED_LENGTH_CLUSTER!(FunctionType, { + 1 + // to-do +}); +DECLARE_FIXED_LENGTH_CLUSTER!(RecordType, { + 1 + // to-do +}); + +DECLARE_FIXED_LENGTH_CLUSTER!(TypeParameter, { + 1 + // to-do +}); +DECLARE_FIXED_LENGTH_CLUSTER!(Closure, { + 1 + // to-do +}); + +DECLARE_FIXED_LENGTH_CLUSTER!(Double, { + 1 + // to-do +}); +DECLARE_FIXED_LENGTH_CLUSTER!(Int32x4, { + 1 + // to-do +}); + +DECLARE_FIXED_LENGTH_CLUSTER!(GrowableObjectArray, { + 1 + // to-do +}); +DECLARE_FIXED_LENGTH_CLUSTER!(TypedDataView, { + 1 + // to-do +}); + +DECLARE_FIXED_LENGTH_CLUSTER!(ExternalTypedData, { + 1 + // to-do +}); +DECLARE_FIXED_LENGTH_CLUSTER!(StackTrace, { + 1 + // to-do +}); + +DECLARE_FIXED_LENGTH_CLUSTER!(RegExp, { + 1 + // to-do +}); +DECLARE_FIXED_LENGTH_CLUSTER!(WeakProperty, { + 1 + // to-do +}); + +DECLARE_FIXED_LENGTH_CLUSTER!(Map, { + 1 + // to-do +}); + +DECLARE_FIXED_LENGTH_CLUSTER!(Set, { 1 // to-do }); -/* -DECLARE_FIXED_LENGTH_CLUSTER!(Mint, 16); -DECLARE_FIXED_LENGTH_CLUSTER!(Double, 16); -DECLARE_FIXED_LENGTH_CLUSTER!(TypeParameter, 32); -DECLARE_FIXED_LENGTH_CLUSTER!(Type, 32); -DECLARE_FIXED_LENGTH_CLUSTER!(TypeArguments, 32); -*/ diff --git a/crates/flutterdec-serwalker/src/constants.rs b/crates/flutterdec-serwalker/src/constants.rs index 6a68d72..3cb5430 100644 --- a/crates/flutterdec-serwalker/src/constants.rs +++ b/crates/flutterdec-serwalker/src/constants.rs @@ -26,183 +26,229 @@ pub const SMI_SHIFT: usize = 1usize; pub const VERSION_HASH_LENGTH: usize = 32usize; -pub enum ClassId { - IllegalCid = 0, - NativePointer, - FreeListElement, - ForwardingCorpse, - ObjectCid, - ClassCid, - PatchClassCid, - FunctionCid, - TypeParametersCid, - ClosureDataCid, - FfiTrampolineDataCid, - FieldCid, - ScriptCid, - LibraryCid, - NamespaceCid, - KernelProgramInfoCid, - WeakSerializationReferenceCid, - WeakArrayCid, - CodeCid, - BytecodeCid, - InstructionsCid, - InstructionsSectionCid, - InstructionsTableCid, - ObjectPoolCid, - PcDescriptorsCid, - CodeSourceMapCid, - CompressedStackMapsCid, - LocalVarDescriptorsCid, - ExceptionHandlersCid, - ContextCid, - ContextScopeCid, - SentinelCid, - SingleTargetCacheCid, - MonomorphicSmiableCallCid, - CallSiteDataCid, - UnlinkedCallCid, - ICDataCid, - MegamorphicCacheCid, - SubtypeTestCacheCid, - LoadingUnitCid, - ErrorCid, - ApiErrorCid, - LanguageErrorCid, - UnhandledExceptionCid, - UnwindErrorCid, - InstanceCid, - LibraryPrefixCid, - TypeArgumentsCid, - AbstractTypeCid, - TypeCid, - FunctionTypeCid, - RecordTypeCid, - TypeParameterCid, - FinalizerBaseCid, - FinalizerCid, - NativeFinalizerCid, - FinalizerEntryCid, - ClosureCid, - NumberCid, - IntegerCid, - SmiCid, - MintCid, - DoubleCid, - BoolCid, - Float32x4Cid, - Int32x4Cid, - Float64x2Cid, - RecordCid, - TypedDataBaseCid, - TypedDataCid, - ExternalTypedDataCid, - TypedDataViewCid, - PointerCid, - DynamicLibraryCid, - CapabilityCid, - ReceivePortCid, - SendPortCid, - StackTraceCid, - SuspendStateCid, - RegExpCid, - WeakPropertyCid, - WeakReferenceCid, - MirrorReferenceCid, - FutureOrCid, - UserTagCid, - TransferableTypedDataCid, - MapCid, - ConstMapCid, - SetCid, - ConstSetCid, - ArrayCid, - ImmutableArrayCid, - GrowableObjectArrayCid, - StringCid, - OneByteStringCid, - TwoByteStringCid, - FfiNativeFunctionCid, - FfiInt8Cid, - FfiInt16Cid, - FfiInt32Cid, - FfiInt64Cid, - FfiUint8Cid, - FfiUint16Cid, - FfiUint32Cid, - FfiUint64Cid, - FfiFloatCid, - FfiDoubleCid, - FfiVoidCid, - FfiHandleCid, - FfiBoolCid, - FfiNativeTypeCid, - FfiStructCid, - TypedDataInt8ArrayCid, - TypedDataInt8ArrayViewCid, - ExternalTypedDataInt8ArrayCid, - UnmodifiableTypedDataInt8ArrayViewCid, - TypedDataUint8ArrayCid, - TypedDataUint8ArrayViewCid, - ExternalTypedDataUint8ArrayCid, - UnmodifiableTypedDataUint8ArrayViewCid, - TypedDataUint8ClampedArrayCid, - TypedDataUint8ClampedArrayViewCid, - ExternalTypedDataUint8ClampedArrayCid, - UnmodifiableTypedDataUint8ClampedArrayViewCid, - TypedDataInt16ArrayCid, - TypedDataInt16ArrayViewCid, - ExternalTypedDataInt16ArrayCid, - UnmodifiableTypedDataInt16ArrayViewCid, - TypedDataUint16ArrayCid, - TypedDataUint16ArrayViewCid, - ExternalTypedDataUint16ArrayCid, - UnmodifiableTypedDataUint16ArrayViewCid, - TypedDataInt32ArrayCid, - TypedDataInt32ArrayViewCid, - ExternalTypedDataInt32ArrayCid, - UnmodifiableTypedDataInt32ArrayViewCid, - TypedDataUint32ArrayCid, - TypedDataUint32ArrayViewCid, - ExternalTypedDataUint32ArrayCid, - UnmodifiableTypedDataUint32ArrayViewCid, - TypedDataInt64ArrayCid, - TypedDataInt64ArrayViewCid, - ExternalTypedDataInt64ArrayCid, - UnmodifiableTypedDataInt64ArrayViewCid, - TypedDataUint64ArrayCid, - TypedDataUint64ArrayViewCid, - ExternalTypedDataUint64ArrayCid, - UnmodifiableTypedDataUint64ArrayViewCid, - TypedDataFloat32ArrayCid, - TypedDataFloat32ArrayViewCid, - ExternalTypedDataFloat32ArrayCid, - UnmodifiableTypedDataFloat32ArrayViewCid, - TypedDataFloat64ArrayCid, - TypedDataFloat64ArrayViewCid, - ExternalTypedDataFloat64ArrayCid, - UnmodifiableTypedDataFloat64ArrayViewCid, - TypedDataFloat32x4ArrayCid, - TypedDataFloat32x4ArrayViewCid, - ExternalTypedDataFloat32x4ArrayCid, - UnmodifiableTypedDataFloat32x4ArrayViewCid, - TypedDataInt32x4ArrayCid, - TypedDataInt32x4ArrayViewCid, - ExternalTypedDataInt32x4ArrayCid, - UnmodifiableTypedDataInt32x4ArrayViewCid, - TypedDataFloat64x2ArrayCid, - TypedDataFloat64x2ArrayViewCid, - ExternalTypedDataFloat64x2ArrayCid, - UnmodifiableTypedDataFloat64x2ArrayViewCid, - ByteDataViewCid, - UnmodifiableByteDataViewCid, - ByteBufferCid, - NullCid, - DynamicCid, - VoidCid, - NeverCid, - NumPredefinedCids, +macro_rules! DEFINE_CLASS_ID { + ( $( $name:ident = $val:expr ),* ) => { + #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] + #[repr(u32)] + pub enum ClassId { + #[default] + IllegalCid = 0, + $( $name = $val, )* + } + + impl TryFrom for ClassId { + type Error = &'static str; + fn try_from(value: u32) -> Result { + match value { + 0 => Ok(ClassId::IllegalCid), + $( $val => Ok(ClassId::$name), )* + _ => Err("Invalid ClassId"), + } + } + } + }; +} + +DEFINE_CLASS_ID! { + NativePointer = 1, + FreeListElement = 2, + ForwardingCorpse = 3, + ObjectCid = 4, + ClassCid = 5, + PatchClassCid = 6, + FunctionCid = 7, + TypeParametersCid = 8, + ClosureDataCid = 9, + FfiTrampolineDataCid = 10, + FieldCid = 11, + ScriptCid = 12, + LibraryCid = 13, + NamespaceCid = 14, + KernelProgramInfoCid = 15, + WeakSerializationReferenceCid = 16, + WeakArrayCid = 17, + CodeCid = 18, + BytecodeCid = 19, + InstructionsCid = 20, + InstructionsSectionCid = 21, + InstructionsTableCid = 22, + ObjectPoolCid = 23, + PcDescriptorsCid = 24, + CodeSourceMapCid = 25, + CompressedStackMapsCid = 26, + LocalVarDescriptorsCid = 27, + ExceptionHandlersCid = 28, + ContextCid = 29, + ContextScopeCid = 30, + SentinelCid = 31, + SingleTargetCacheCid = 32, + MonomorphicSmiableCallCid = 33, + CallSiteDataCid = 34, + UnlinkedCallCid = 35, + ICDataCid = 36, + MegamorphicCacheCid = 37, + SubtypeTestCacheCid = 38, + LoadingUnitCid = 39, + ErrorCid = 40, + ApiErrorCid = 41, + LanguageErrorCid = 42, + UnhandledExceptionCid = 43, + UnwindErrorCid = 44, + InstanceCid = 45, + LibraryPrefixCid = 46, + TypeArgumentsCid = 47, + AbstractTypeCid = 48, + TypeCid = 49, + FunctionTypeCid = 50, + RecordTypeCid = 51, + TypeParameterCid = 52, + FinalizerBaseCid = 53, + FinalizerCid = 54, + NativeFinalizerCid = 55, + FinalizerEntryCid = 56, + ClosureCid = 57, + NumberCid = 58, + IntegerCid = 59, + SmiCid = 60, + MintCid = 61, + DoubleCid = 62, + BoolCid = 63, + Float32x4Cid = 64, + Int32x4Cid = 65, + Float64x2Cid = 66, + RecordCid = 67, + TypedDataBaseCid = 68, + TypedDataCid = 69, + ExternalTypedDataCid = 70, + TypedDataViewCid = 71, + PointerCid = 72, + DynamicLibraryCid = 73, + CapabilityCid = 74, + ReceivePortCid = 75, + SendPortCid = 76, + StackTraceCid = 77, + SuspendStateCid = 78, + RegExpCid = 79, + WeakPropertyCid = 80, + WeakReferenceCid = 81, + MirrorReferenceCid = 82, + FutureOrCid = 83, + UserTagCid = 84, + TransferableTypedDataCid = 85, + MapCid = 86, + ConstMapCid = 87, + SetCid = 88, + ConstSetCid = 89, + ArrayCid = 90, + ImmutableArrayCid = 91, + GrowableObjectArrayCid = 92, + _StringCid = 93, + OneByteStringCid = 94, + TwoByteStringCid = 95, + FfiNativeFunctionCid = 96, + FfiInt8Cid = 97, + FfiInt16Cid = 98, + FfiInt32Cid = 99, + FfiInt64Cid = 100, + FfiUint8Cid = 101, + FfiUint16Cid = 102, + FfiUint32Cid = 103, + FfiUint64Cid = 104, + FfiFloatCid = 105, + FfiDoubleCid = 106, + FfiVoidCid = 107, + FfiHandleCid = 108, + FfiBoolCid = 109, + FfiNativeTypeCid = 110, + FfiStructCid = 111, + TypedDataInt8ArrayCid = 112, + TypedDataInt8ArrayViewCid = 113, + ExternalTypedDataInt8ArrayCid = 114, + UnmodifiableTypedDataInt8ArrayViewCid = 115, + TypedDataUint8ArrayCid = 116, + TypedDataUint8ArrayViewCid = 117, + ExternalTypedDataUint8ArrayCid = 118, + UnmodifiableTypedDataUint8ArrayViewCid = 119, + TypedDataUint8ClampedArrayCid = 120, + TypedDataUint8ClampedArrayViewCid = 121, + ExternalTypedDataUint8ClampedArrayCid = 122, + UnmodifiableTypedDataUint8ClampedArrayViewCid = 123, + TypedDataInt16ArrayCid = 124, + TypedDataInt16ArrayViewCid = 125, + ExternalTypedDataInt16ArrayCid = 126, + UnmodifiableTypedDataInt16ArrayViewCid = 127, + TypedDataUint16ArrayCid = 128, + TypedDataUint16ArrayViewCid = 129, + ExternalTypedDataUint16ArrayCid = 130, + UnmodifiableTypedDataUint16ArrayViewCid = 131, + TypedDataInt32ArrayCid = 132, + TypedDataInt32ArrayViewCid = 133, + ExternalTypedDataInt32ArrayCid = 134, + UnmodifiableTypedDataInt32ArrayViewCid = 135, + TypedDataUint32ArrayCid = 136, + TypedDataUint32ArrayViewCid = 137, + ExternalTypedDataUint32ArrayCid = 138, + UnmodifiableTypedDataUint32ArrayViewCid = 139, + TypedDataInt64ArrayCid = 140, + TypedDataInt64ArrayViewCid = 141, + ExternalTypedDataInt64ArrayCid = 142, + UnmodifiableTypedDataInt64ArrayViewCid = 143, + TypedDataUint64ArrayCid = 144, + TypedDataUint64ArrayViewCid = 145, + ExternalTypedDataUint64ArrayCid = 146, + UnmodifiableTypedDataUint64ArrayViewCid = 147, + TypedDataFloat32ArrayCid = 148, + TypedDataFloat32ArrayViewCid = 149, + ExternalTypedDataFloat32ArrayCid = 150, + UnmodifiableTypedDataFloat32ArrayViewCid = 151, + TypedDataFloat64ArrayCid = 152, + TypedDataFloat64ArrayViewCid = 153, + ExternalTypedDataFloat64ArrayCid = 154, + UnmodifiableTypedDataFloat64ArrayViewCid = 155, + TypedDataFloat32x4ArrayCid = 156, + TypedDataFloat32x4ArrayViewCid = 157, + ExternalTypedDataFloat32x4ArrayCid = 158, + UnmodifiableTypedDataFloat32x4ArrayViewCid = 159, + TypedDataInt32x4ArrayCid = 160, + TypedDataInt32x4ArrayViewCid = 161, + ExternalTypedDataInt32x4ArrayCid = 162, + UnmodifiableTypedDataInt32x4ArrayViewCid = 163, + TypedDataFloat64x2ArrayCid = 164, + TypedDataFloat64x2ArrayViewCid = 165, + ExternalTypedDataFloat64x2ArrayCid = 166, + UnmodifiableTypedDataFloat64x2ArrayViewCid = 167, + ByteDataViewCid = 168, + UnmodifiableByteDataViewCid = 169, + ByteBufferCid = 170, + NullCid = 171, + DynamicCid = 172, + VoidCid = 173, + NeverCid = 174, + NumPredefinedCids = 175 +} + +#[macro_export] +macro_rules! FFI_TYPES_LIST { + ($callback:ident) => { + $callback! { + FfiNativeFunctionCid, + FfiInt8Cid, + FfiInt16Cid, + FfiInt32Cid, + FfiInt64Cid, + FfiUint8Cid, + FfiUint16Cid, + FfiUint32Cid, + FfiUint64Cid, + FfiFloatCid, + FfiDoubleCid, + FfiVoidCid, + FfiHandleCid, + FfiBoolCid, + FfiNativeTypeCid, + FfiStructCid + } + }; } /* diff --git a/crates/flutterdec-serwalker/src/raw_object/mod.rs b/crates/flutterdec-serwalker/src/raw_object/mod.rs index 655e79e..63ea72a 100644 --- a/crates/flutterdec-serwalker/src/raw_object/mod.rs +++ b/crates/flutterdec-serwalker/src/raw_object/mod.rs @@ -1,4 +1,4 @@ -type Smi = i64; // Using i64 for Smi fields to be decompressed +type Smi = i32; // Using i64 for Smi fields to be decompressed // --- Fixed-Size Objects with defined fields --- diff --git a/crates/flutterdec-serwalker/src/snapshot.rs b/crates/flutterdec-serwalker/src/snapshot.rs index b7d7cb5..2374f87 100644 --- a/crates/flutterdec-serwalker/src/snapshot.rs +++ b/crates/flutterdec-serwalker/src/snapshot.rs @@ -1,6 +1,9 @@ -use crate::cluster::Cluster; -use crate::constants::{self, MAGIC_BYTES, UNSIGNED_M}; +use std::collections::HashMap; + +use crate::cluster::{self, decide_cluster, Cluster}; +use crate::constants::{self, ClassId, MAGIC_BYTES, UNSIGNED_M}; use crate::stream::Stream; +use crate::utils::{decode_tags, DecodedTags}; #[derive(Default)] enum SnapshotKind @@ -33,9 +36,11 @@ impl TryFrom for SnapshotKind { } } -struct DataSnapshot { +#[derive(Default)] +pub struct DataSnapshot { // this array will contain mutable references to all clusters, and it will be indexed using the class id - clusters: [Option>; constants::MAX_CLUSTER_NUM], // thus each cluster must have its own UNIQUE class id + clusters: HashMap>, // thus each cluster must have its own UNIQUE class id + cluster_order: Vec, // used in the fill step to know which cluster's read_fill function to call magic_bytes: u32, size: u64, @@ -55,27 +60,6 @@ struct DataSnapshot { start_of_fill_area: usize, } -impl Default for DataSnapshot { - fn default() -> Self { - const INIT_CLUSTER: Option> = None; - Self { - clusters: [INIT_CLUSTER; constants::MAX_CLUSTER_NUM], - magic_bytes: 0, - size: 0, - kind: SnapshotKind::default(), - version_hash: String::new(), - features: String::new(), - num_base_objects: 0, - num_objects: 0, - num_clusters: 0, - instr_table_len: 0, - instr_table_offset: 0, - start_of_alloc_area: 0, - start_of_fill_area: 0, - } - } -} - impl DataSnapshot { fn parse_version_and_features(&mut self, stream: &mut Stream) { let mut version_and_features = stream.read_c_string(); @@ -107,11 +91,37 @@ impl DataSnapshot { } fn parse_clusters(&mut self, stream: &mut Stream) { - let curr_ref_id: u64 = 0; // all objects are numbered starting from 0 - } + let mut curr_ref_id: u64 = 0; // all objects are numbered starting from 0 + + for _cluster_idx in 0..self.num_clusters { + let tags: u32 = stream.read_modified_leb128(UNSIGNED_M) as u32; + let decoded_tags: DecodedTags = decode_tags(tags); + let cid = decoded_tags.get_cid(); + + let mut cluster = + decide_cluster(cid).expect("Couldn't find cluster implementation for class {cid}"); - pub fn parse_snapshot(&mut self, stream: &mut Stream) { - println!("Now parsing the snapshot..."); - self.parse_header(stream); + cluster.read_alloc(&mut curr_ref_id, stream); + self.clusters.insert(cid, cluster); // hashmap takes ownership of box + self.cluster_order.push(cid); + } + + for cluster_idx in 0..self.num_clusters { + let cid = self.cluster_order[cluster_idx as usize]; + let cluster_wrapper = self.clusters.get_mut(&cid); + + let cluster = cluster_wrapper.unwrap(); // this should never panic + (*cluster).read_fill(&mut curr_ref_id, stream); + } } } + +pub fn parse_snapshot(stream: &mut Stream) -> DataSnapshot { + let mut snapshot = DataSnapshot::default(); + + println!("Now parsing the snapshot..."); + snapshot.parse_header(stream); + snapshot.parse_clusters(stream); + + snapshot +} diff --git a/crates/flutterdec-serwalker/src/stream.rs b/crates/flutterdec-serwalker/src/stream.rs index 06c2aed..35681ac 100644 --- a/crates/flutterdec-serwalker/src/stream.rs +++ b/crates/flutterdec-serwalker/src/stream.rs @@ -1,7 +1,4 @@ -use crate::constants::{ - DATA_BITS_PER_BYTE, SIGNED_END_OF_DATA_BYTE, UNSIGNED_END_OF_DATA_BYTE, - UNSIGNED_MAX_DATA_PER_BYTE, -}; +use crate::constants::{DATA_BITS_PER_BYTE, UNSIGNED_END_OF_DATA_BYTE, UNSIGNED_MAX_DATA_PER_BYTE}; pub struct Stream<'a> { byte_stream: &'a [u8], curr_stream_offset: usize, @@ -153,6 +150,6 @@ impl<'a> Stream<'a> { self.advance_pos(idx); - (ref_id + 128) as u32 + (ref_id + 128) as u32 // ref_ids are always unsigned } } diff --git a/crates/flutterdec-serwalker/src/utils.rs b/crates/flutterdec-serwalker/src/utils.rs index 4f7eb30..ee6f0c7 100644 --- a/crates/flutterdec-serwalker/src/utils.rs +++ b/crates/flutterdec-serwalker/src/utils.rs @@ -1,13 +1,12 @@ -use crate::cluster::Cluster; -use crate::constants::UNSIGNED_M; -use crate::stream::Stream; use paste::paste; +use crate::constants::ClassId; + #[macro_export] macro_rules! DECLARE_FIXED_LENGTH_CLUSTER { ($name:ident, $fill_impl:block) => { ::paste::paste! { // this is ugly, but the language doesn't support identifier concatenation - struct [<$name Cluster>] + pub struct [<$name Cluster>] { tags: u32, obj_count: u64, @@ -18,6 +17,8 @@ macro_rules! DECLARE_FIXED_LENGTH_CLUSTER { end_of_fill: usize, end_of_alloc: usize, + first_ref_id: u32, + objs: Vec<(u64, Box<$name>)> // a pair (ref_id, object) } @@ -51,3 +52,55 @@ macro_rules! DECLARE_FIXED_LENGTH_CLUSTER { } }; } + +pub struct DecodedTags { + class_id: ClassId, + is_deeply_immutable: bool, // we don't really care about this for now, at least + is_canonical: bool, +} + +impl DecodedTags { + pub fn new(cid: ClassId, immut: bool, canonical: bool) -> Self { + Self { + class_id: cid, + is_deeply_immutable: immut, + is_canonical: canonical, + } + } + + pub fn get_cid(&self) -> ClassId { + self.class_id + } + + pub fn is_deeply_immutable(&self) -> bool { + self.is_deeply_immutable + } + + pub fn is_canonical(&self) -> bool { + self.is_canonical + } +} + +macro_rules! DECODE_IS_CID { + ($tags:expr) => { + ClassId::try_from(($tags >> 12) & 0xFFFFF) + }; +} +macro_rules! DECODE_IS_DEEPLY_IMMUTABLE { + ($tags:expr) => { + (($tags >> 7) & 0x1) == 1 + }; +} +macro_rules! DECODE_IS_CANONICAL { + ($tags:expr) => { + (($tags >> 1) & 0x1) == 1 + }; +} + +pub fn decode_tags(tags: u32) -> DecodedTags { + let class_id: ClassId = DECODE_IS_CID!(tags).unwrap(); + let is_deeply_immutable: bool = DECODE_IS_DEEPLY_IMMUTABLE!(tags); + let is_canonical: bool = DECODE_IS_CANONICAL!(tags); + + DecodedTags::new(class_id, is_deeply_immutable, is_canonical) +} From 5db964118d39012ce3a40f7bb4f1f17c729c4190 Mon Sep 17 00:00:00 2001 From: kalixtez Date: Mon, 4 May 2026 01:41:40 -0500 Subject: [PATCH 22/50] feat(serwalker): added forward declarations for the remaining clusters and serializable types --- .../flutterdec-serwalker/src/cluster/mod.rs | 219 +++++------------- .../src/raw_object/mod.rs | 59 +++++ crates/flutterdec-serwalker/src/snapshot.rs | 17 +- crates/flutterdec-serwalker/src/utils.rs | 59 ++++- 4 files changed, 178 insertions(+), 176 deletions(-) diff --git a/crates/flutterdec-serwalker/src/cluster/mod.rs b/crates/flutterdec-serwalker/src/cluster/mod.rs index 8bb1927..c856999 100644 --- a/crates/flutterdec-serwalker/src/cluster/mod.rs +++ b/crates/flutterdec-serwalker/src/cluster/mod.rs @@ -2,6 +2,7 @@ use crate::constants::{ClassId, ClassId::*, SIGNED_M, UNSIGNED_M}; use crate::raw_object::*; use crate::stream::Stream; use crate::DECLARE_FIXED_LENGTH_CLUSTER; +use crate::DECLARE_VARIABLE_LENGTH_CLUSTER; use crate::FFI_TYPES_LIST; type Smi = i32; @@ -9,15 +10,7 @@ type Smi = i32; pub trait Cluster { fn is_fixed_len(&self) -> bool; fn read_alloc(&mut self, last_ref_id: &mut u64, stream: &mut Stream) -> usize; - fn read_fill(&mut self, last_ref_id: &mut u64, stream: &mut Stream) -> usize; -} - -pub fn read_cluster_alloc() { - //let curr_ref_id: u64 = 0; -} - -pub fn read_cluster_fill() { - //let curr_ref_id: u64 = 0; + fn read_fill(&mut self, stream: &mut Stream) -> usize; } pub fn read_smi(stream: &mut Stream) -> Smi { @@ -45,153 +38,61 @@ pub fn decide_cluster(class_id: ClassId) -> Result, &'static st // whose fill cluster size is uniquely determined by sizeof(Object) * num_of_objects // and alloc cluster size is tags (MULEB128) + num_of_objects (MULEB128) -DECLARE_FIXED_LENGTH_CLUSTER!(TypeParameters, { - 1 - // to-do -}); - -DECLARE_FIXED_LENGTH_CLUSTER!(PatchClass, { - 1 - // to-do -}); - -DECLARE_FIXED_LENGTH_CLUSTER!(Function, { - 1 - // to-do -}); - -DECLARE_FIXED_LENGTH_CLUSTER!(ClosureData, { - 1 - // to-do -}); -DECLARE_FIXED_LENGTH_CLUSTER!(FfiTrampolineData, { - 1 - // to-do -}); - -DECLARE_FIXED_LENGTH_CLUSTER!(Field, { - 1 - // to-do -}); -DECLARE_FIXED_LENGTH_CLUSTER!(Script, { - 1 - // to-do -}); - -DECLARE_FIXED_LENGTH_CLUSTER!(Library, { - 1 - // to-do -}); -DECLARE_FIXED_LENGTH_CLUSTER!(Namespace, { - 1 - // to-do -}); - -DECLARE_FIXED_LENGTH_CLUSTER!(KernelProgramInfo, { - 1 - // to-do -}); -DECLARE_FIXED_LENGTH_CLUSTER!(UnlinkedCall, { - 1 - // to-do -}); - -DECLARE_FIXED_LENGTH_CLUSTER!(ICData, { - 1 - // to-do -}); -DECLARE_FIXED_LENGTH_CLUSTER!(MegamorphicCache, { - 1 - // to-do -}); - -DECLARE_FIXED_LENGTH_CLUSTER!(SubtypeTestCache, { - 1 - // to-do -}); -DECLARE_FIXED_LENGTH_CLUSTER!(LoadingUnit, { - 1 - // to-do -}); - -DECLARE_FIXED_LENGTH_CLUSTER!(LanguageError, { - 1 - // to-do -}); -DECLARE_FIXED_LENGTH_CLUSTER!(UnhandledException, { - 1 - // to-do -}); - -DECLARE_FIXED_LENGTH_CLUSTER!(LibraryPrefix, { - 1 - // to-do -}); -DECLARE_FIXED_LENGTH_CLUSTER!(Type, { - 1 - // to-do -}); - -DECLARE_FIXED_LENGTH_CLUSTER!(FunctionType, { - 1 - // to-do -}); -DECLARE_FIXED_LENGTH_CLUSTER!(RecordType, { - 1 - // to-do -}); - -DECLARE_FIXED_LENGTH_CLUSTER!(TypeParameter, { - 1 - // to-do -}); -DECLARE_FIXED_LENGTH_CLUSTER!(Closure, { - 1 - // to-do -}); - -DECLARE_FIXED_LENGTH_CLUSTER!(Double, { - 1 - // to-do -}); -DECLARE_FIXED_LENGTH_CLUSTER!(Int32x4, { - 1 - // to-do -}); - -DECLARE_FIXED_LENGTH_CLUSTER!(GrowableObjectArray, { - 1 - // to-do -}); -DECLARE_FIXED_LENGTH_CLUSTER!(TypedDataView, { - 1 - // to-do -}); - -DECLARE_FIXED_LENGTH_CLUSTER!(ExternalTypedData, { - 1 - // to-do -}); -DECLARE_FIXED_LENGTH_CLUSTER!(StackTrace, { - 1 - // to-do -}); - -DECLARE_FIXED_LENGTH_CLUSTER!(RegExp, { - 1 - // to-do -}); -DECLARE_FIXED_LENGTH_CLUSTER!(WeakProperty, { - 1 - // to-do -}); - -DECLARE_FIXED_LENGTH_CLUSTER!(Map, { - 1 - // to-do -}); - -DECLARE_FIXED_LENGTH_CLUSTER!(Set, { - 1 - // to-do -}); +DECLARE_FIXED_LENGTH_CLUSTER!(TypeParameters<'a>, |_self, last_ref_id, stream| { 1 }); +DECLARE_FIXED_LENGTH_CLUSTER!(PatchClass<'a>, |_self, last_ref_id, stream| { 1 }); +DECLARE_FIXED_LENGTH_CLUSTER!(Function<'a>, |_self, last_ref_id, stream| { 1 }); +DECLARE_FIXED_LENGTH_CLUSTER!(ClosureData<'a>, |_self, last_ref_id, stream| { 1 }); +DECLARE_FIXED_LENGTH_CLUSTER!(FfiTrampolineData<'a>, |_self, last_ref_id, stream| { 1 }); +DECLARE_FIXED_LENGTH_CLUSTER!(Field<'a>, |_self, last_ref_id, stream| { 1 }); +DECLARE_FIXED_LENGTH_CLUSTER!(Script<'a>, |_self, last_ref_id, stream| { 1 }); +DECLARE_FIXED_LENGTH_CLUSTER!(Library<'a>, |_self, last_ref_id, stream| { 1 }); +DECLARE_FIXED_LENGTH_CLUSTER!(Namespace<'a>, |_self, last_ref_id, stream| { 1 }); +DECLARE_FIXED_LENGTH_CLUSTER!(KernelProgramInfo<'a>, |_self, last_ref_id, stream| { 1 }); +DECLARE_FIXED_LENGTH_CLUSTER!(UnlinkedCall, |_self, last_ref_id, stream| { 1 }); +DECLARE_FIXED_LENGTH_CLUSTER!(ICData, |_self, last_ref_id, stream| { 1 }); +DECLARE_FIXED_LENGTH_CLUSTER!(MegamorphicCache, |_self, last_ref_id, stream| { 1 }); +DECLARE_FIXED_LENGTH_CLUSTER!(SubtypeTestCache, |_self, last_ref_id, stream| { 1 }); +DECLARE_FIXED_LENGTH_CLUSTER!(LoadingUnit<'a>, |_self, last_ref_id, stream| { 1 }); +DECLARE_FIXED_LENGTH_CLUSTER!(LanguageError, |_self, last_ref_id, stream| { 1 }); +DECLARE_FIXED_LENGTH_CLUSTER!(UnhandledException, |_self, last_ref_id, stream| { 1 }); +DECLARE_FIXED_LENGTH_CLUSTER!(LibraryPrefix, |_self, last_ref_id, stream| { 1 }); +DECLARE_FIXED_LENGTH_CLUSTER!(Type<'a>, |_self, last_ref_id, stream| { 1 }); +DECLARE_FIXED_LENGTH_CLUSTER!(FunctionType<'a>, |_self, last_ref_id, stream| { 1 }); +DECLARE_FIXED_LENGTH_CLUSTER!(RecordType, |_self, last_ref_id, stream| { 1 }); +DECLARE_FIXED_LENGTH_CLUSTER!(TypeParameter<'a>, |_self, last_ref_id, stream| { 1 }); +DECLARE_FIXED_LENGTH_CLUSTER!(Closure<'a>, |_self, last_ref_id, stream| { 1 }); +DECLARE_FIXED_LENGTH_CLUSTER!(Double, |_self, last_ref_id, stream| { 1 }); +DECLARE_FIXED_LENGTH_CLUSTER!(Int32x4, |_self, last_ref_id, stream| { 1 }); +DECLARE_FIXED_LENGTH_CLUSTER!(GrowableObjectArray<'a>, |_self, last_ref_id, stream| { 1 }); +DECLARE_FIXED_LENGTH_CLUSTER!(TypedDataView<'a>, |_self, last_ref_id, stream| { 1 }); +DECLARE_FIXED_LENGTH_CLUSTER!(ExternalTypedData, |_self, last_ref_id, stream| { 1 }); +DECLARE_FIXED_LENGTH_CLUSTER!(StackTrace, |_self, last_ref_id, stream| { 1 }); +DECLARE_FIXED_LENGTH_CLUSTER!(RegExp, |_self, last_ref_id, stream| { 1 }); +DECLARE_FIXED_LENGTH_CLUSTER!(WeakProperty, |_self, last_ref_id, stream| { 1 }); + +DECLARE_VARIABLE_LENGTH_CLUSTER!(Map); +DECLARE_VARIABLE_LENGTH_CLUSTER!(Set); +DECLARE_VARIABLE_LENGTH_CLUSTER!(Instance<'a>); +DECLARE_VARIABLE_LENGTH_CLUSTER!(TypedData<'a>); +DECLARE_VARIABLE_LENGTH_CLUSTER!(Class<'a>); +DECLARE_VARIABLE_LENGTH_CLUSTER!(TypeArguments<'a>); +DECLARE_VARIABLE_LENGTH_CLUSTER!(Code<'a>); +DECLARE_VARIABLE_LENGTH_CLUSTER!(ObjectPool); +DECLARE_VARIABLE_LENGTH_CLUSTER!(ExceptionHandlers<'a>); +DECLARE_VARIABLE_LENGTH_CLUSTER!(Context<'a>); +DECLARE_VARIABLE_LENGTH_CLUSTER!(ContextScope); +DECLARE_VARIABLE_LENGTH_CLUSTER!(Mint); +DECLARE_VARIABLE_LENGTH_CLUSTER!(Float32x4); +DECLARE_VARIABLE_LENGTH_CLUSTER!(Float64x2); +DECLARE_VARIABLE_LENGTH_CLUSTER!(Record); +DECLARE_VARIABLE_LENGTH_CLUSTER!(Array<'a>); +DECLARE_VARIABLE_LENGTH_CLUSTER!(WeakArray<'a>); +DECLARE_VARIABLE_LENGTH_CLUSTER!(ImmutableArray); +DECLARE_VARIABLE_LENGTH_CLUSTER!(ConstMap); +DECLARE_VARIABLE_LENGTH_CLUSTER!(ConstSet); +DECLARE_VARIABLE_LENGTH_CLUSTER!(CodeSourceMap); +DECLARE_VARIABLE_LENGTH_CLUSTER!(CompressedStackMaps); +DECLARE_VARIABLE_LENGTH_CLUSTER!(PcDescriptors); +DECLARE_VARIABLE_LENGTH_CLUSTER!(OneByteString); +DECLARE_VARIABLE_LENGTH_CLUSTER!(TwoByteString); +DECLARE_VARIABLE_LENGTH_CLUSTER!(_String); diff --git a/crates/flutterdec-serwalker/src/raw_object/mod.rs b/crates/flutterdec-serwalker/src/raw_object/mod.rs index 63ea72a..49f0429 100644 --- a/crates/flutterdec-serwalker/src/raw_object/mod.rs +++ b/crates/flutterdec-serwalker/src/raw_object/mod.rs @@ -223,45 +223,104 @@ pub struct ObjectPool; // Placeholder structs for references used above that aren't defined yet // it wouldn't compile without this, though for now they remain unimplemented... +#[derive(Default)] pub struct Array<'a> { _marker: std::marker::PhantomData<&'a ()>, } +#[derive(Default)] pub struct Object<'a> { _marker: std::marker::PhantomData<&'a ()>, } +#[derive(Default)] pub struct AbstractType<'a> { _marker: std::marker::PhantomData<&'a ()>, } +#[derive(Default)] pub struct FunctionType<'a> { _marker: std::marker::PhantomData<&'a ()>, } +#[derive(Default)] pub struct Script<'a> { _marker: std::marker::PhantomData<&'a ()>, } +#[derive(Default)] pub struct Closure<'a> { _marker: std::marker::PhantomData<&'a ()>, } +#[derive(Default)] pub struct Instance<'a> { _marker: std::marker::PhantomData<&'a ()>, } +#[derive(Default)] pub struct WeakArray<'a> { _marker: std::marker::PhantomData<&'a ()>, } +#[derive(Default)] pub struct TypedDataBase<'a> { _marker: std::marker::PhantomData<&'a ()>, } +#[derive(Default)] pub struct TypedData<'a> { _marker: std::marker::PhantomData<&'a ()>, } +#[derive(Default)] pub struct TypedDataView<'a> { _marker: std::marker::PhantomData<&'a ()>, } +#[derive(Default)] pub struct GrowableObjectArray<'a> { _marker: std::marker::PhantomData<&'a ()>, } +#[derive(Default)] pub struct Code<'a> { _marker: std::marker::PhantomData<&'a ()>, } +#[derive(Default)] pub struct LoadingUnit<'a> { _marker: std::marker::PhantomData<&'a ()>, } + +#[derive(Default)] +pub struct ICData; +#[derive(Default)] +pub struct MegamorphicCache; +#[derive(Default)] +pub struct SubtypeTestCache; +#[derive(Default)] +pub struct LanguageError; +#[derive(Default)] +pub struct UnhandledException; +#[derive(Default)] +pub struct LibraryPrefix; +#[derive(Default)] +pub struct RecordType; +#[derive(Default)] +pub struct Int32x4; +#[derive(Default)] +pub struct ExternalTypedData; +#[derive(Default)] +pub struct StackTrace; +#[derive(Default)] +pub struct RegExp; +#[derive(Default)] +pub struct WeakProperty; +#[derive(Default)] +pub struct Map; +#[derive(Default)] +pub struct Set; +#[derive(Default)] +pub struct Float32x4; +#[derive(Default)] +pub struct Float64x2; +#[derive(Default)] +pub struct ConstMap; +#[derive(Default)] +pub struct ConstSet; +#[derive(Default)] +pub struct Record; +#[derive(Default)] +pub struct ImmutableArray; +#[derive(Default)] +pub struct OneByteString; +#[derive(Default)] +pub struct TwoByteString; diff --git a/crates/flutterdec-serwalker/src/snapshot.rs b/crates/flutterdec-serwalker/src/snapshot.rs index 2374f87..c4cbe5c 100644 --- a/crates/flutterdec-serwalker/src/snapshot.rs +++ b/crates/flutterdec-serwalker/src/snapshot.rs @@ -1,6 +1,6 @@ use std::collections::HashMap; -use crate::cluster::{self, decide_cluster, Cluster}; +use crate::cluster::{decide_cluster, Cluster}; use crate::constants::{self, ClassId, MAGIC_BYTES, UNSIGNED_M}; use crate::stream::Stream; use crate::utils::{decode_tags, DecodedTags}; @@ -58,6 +58,9 @@ pub struct DataSnapshot { start_of_alloc_area: usize, start_of_fill_area: usize, + + end_of_alloc_area: usize, + end_of_fill_area: usize, } impl DataSnapshot { @@ -76,7 +79,7 @@ impl DataSnapshot { } self.size = stream.read_u64(); - self.kind = SnapshotKind::try_from(stream.read_u64()).expect("Not a valid snapshot!"); + self.kind = SnapshotKind::try_from(stream.read_u64()).unwrap(); self.parse_version_and_features(stream); @@ -86,13 +89,12 @@ impl DataSnapshot { self.instr_table_len = stream.read_modified_leb128(UNSIGNED_M) as usize; self.instr_table_offset = stream.read_modified_leb128(UNSIGNED_M) as usize; - - self.start_of_alloc_area = stream.get_current_pos(); } fn parse_clusters(&mut self, stream: &mut Stream) { let mut curr_ref_id: u64 = 0; // all objects are numbered starting from 0 + self.start_of_alloc_area = stream.get_current_pos(); for _cluster_idx in 0..self.num_clusters { let tags: u32 = stream.read_modified_leb128(UNSIGNED_M) as u32; let decoded_tags: DecodedTags = decode_tags(tags); @@ -105,15 +107,20 @@ impl DataSnapshot { self.clusters.insert(cid, cluster); // hashmap takes ownership of box self.cluster_order.push(cid); } + self.end_of_alloc_area = stream.get_current_pos(); + self.start_of_fill_area = stream.get_current_pos(); for cluster_idx in 0..self.num_clusters { let cid = self.cluster_order[cluster_idx as usize]; let cluster_wrapper = self.clusters.get_mut(&cid); let cluster = cluster_wrapper.unwrap(); // this should never panic - (*cluster).read_fill(&mut curr_ref_id, stream); + (*cluster).read_fill(stream); } + self.end_of_fill_area = stream.get_current_pos(); } + + fn parse_roots(&mut self, stream: &mut Stream) {} } pub fn parse_snapshot(stream: &mut Stream) -> DataSnapshot { diff --git a/crates/flutterdec-serwalker/src/utils.rs b/crates/flutterdec-serwalker/src/utils.rs index ee6f0c7..4fe79b1 100644 --- a/crates/flutterdec-serwalker/src/utils.rs +++ b/crates/flutterdec-serwalker/src/utils.rs @@ -4,9 +4,9 @@ use crate::constants::ClassId; #[macro_export] macro_rules! DECLARE_FIXED_LENGTH_CLUSTER { - ($name:ident, $fill_impl:block) => { + ($name:ident $(<$lt:lifetime>)?, |$_self:ident, $last_ref_id:ident, $stream:ident| $fill_impl:block) => { ::paste::paste! { // this is ugly, but the language doesn't support identifier concatenation - pub struct [<$name Cluster>] + pub struct [<$name Cluster>] $(<$lt>)? { tags: u32, obj_count: u64, @@ -19,38 +19,73 @@ macro_rules! DECLARE_FIXED_LENGTH_CLUSTER { first_ref_id: u32, - objs: Vec<(u64, Box<$name>)> // a pair (ref_id, object) + objs: Vec)? >> // a pair (ref_id, object) } - impl Cluster for [<$name Cluster>] + impl $(<$lt>)? Cluster for [<$name Cluster>] $(<$lt>)? // optional lifetime parameter { fn read_alloc(&mut self, last_ref_id: &mut u64, stream: &mut Stream) -> usize // read tags and count { - let initial_pos = stream.get_current_pos(); - self.start_of_alloc = initial_pos; + self.start_of_alloc = stream.get_current_pos(); + self.first_ref_id = *last_ref_id as u32; // later used to index the objs vector self.obj_count = stream.read_modified_leb128(UNSIGNED_M); - for obj_idx in 0..self.obj_count + for _obj_idx in 0..self.obj_count { - self.objs.push((*last_ref_id + obj_idx, Box::<$name>::default())); + self.objs.push(Box::<$name $(<$lt>)? >::default()); } *last_ref_id = *last_ref_id + self.obj_count; + self.end_of_alloc = stream.get_current_pos(); - stream.get_current_pos() - initial_pos + self.end_of_alloc - self.start_of_alloc } - fn read_fill(&mut self, last_ref_id: &mut u64, stream: &mut Stream) -> usize - $fill_impl + fn read_fill(&mut self, stream: &mut Stream) -> usize + { + self.start_of_fill = stream.get_current_pos(); + + let $_self = self; + let $stream = stream; + + let fill_size = $fill_impl; + + $_self.end_of_fill = $stream.get_current_pos(); + fill_size + } fn is_fixed_len(&self) -> bool { true } } + } - }; + } +} + +#[macro_export] +macro_rules! DECLARE_VARIABLE_LENGTH_CLUSTER { + ($name:ident $(<$lt:lifetime>)?) => { + ::paste::paste! { + pub struct [<$name Cluster>] $(<$lt>)? + { + tags: u32, + obj_count: u64, + + start_of_fill: usize, + start_of_alloc: usize, + + end_of_fill: usize, + end_of_alloc: usize, + + first_ref_id: u32, + + objs: Vec)? >> // a pair (ref_id, object) + } + } + } } pub struct DecodedTags { From ab6e638948618429b714c4c5d69fc8a412a96279 Mon Sep 17 00:00:00 2001 From: kalixtez Date: Thu, 14 May 2026 16:46:02 -0500 Subject: [PATCH 23/50] feat(serwalker): refactored the raw object representation to resolve Rust's complains about cyclic references and other headaches, implemented read_fill for fixed-size objects --- .../flutterdec-serwalker/src/cluster/mod.rs | 330 +++++++++-- .../src/raw_object/mod.rs | 533 +++++++++++------- crates/flutterdec-serwalker/src/snapshot.rs | 4 +- crates/flutterdec-serwalker/src/utils.rs | 31 +- 4 files changed, 636 insertions(+), 262 deletions(-) diff --git a/crates/flutterdec-serwalker/src/cluster/mod.rs b/crates/flutterdec-serwalker/src/cluster/mod.rs index c856999..9a0dbba 100644 --- a/crates/flutterdec-serwalker/src/cluster/mod.rs +++ b/crates/flutterdec-serwalker/src/cluster/mod.rs @@ -38,55 +38,305 @@ pub fn decide_cluster(class_id: ClassId) -> Result, &'static st // whose fill cluster size is uniquely determined by sizeof(Object) * num_of_objects // and alloc cluster size is tags (MULEB128) + num_of_objects (MULEB128) -DECLARE_FIXED_LENGTH_CLUSTER!(TypeParameters<'a>, |_self, last_ref_id, stream| { 1 }); -DECLARE_FIXED_LENGTH_CLUSTER!(PatchClass<'a>, |_self, last_ref_id, stream| { 1 }); -DECLARE_FIXED_LENGTH_CLUSTER!(Function<'a>, |_self, last_ref_id, stream| { 1 }); -DECLARE_FIXED_LENGTH_CLUSTER!(ClosureData<'a>, |_self, last_ref_id, stream| { 1 }); -DECLARE_FIXED_LENGTH_CLUSTER!(FfiTrampolineData<'a>, |_self, last_ref_id, stream| { 1 }); -DECLARE_FIXED_LENGTH_CLUSTER!(Field<'a>, |_self, last_ref_id, stream| { 1 }); -DECLARE_FIXED_LENGTH_CLUSTER!(Script<'a>, |_self, last_ref_id, stream| { 1 }); -DECLARE_FIXED_LENGTH_CLUSTER!(Library<'a>, |_self, last_ref_id, stream| { 1 }); -DECLARE_FIXED_LENGTH_CLUSTER!(Namespace<'a>, |_self, last_ref_id, stream| { 1 }); -DECLARE_FIXED_LENGTH_CLUSTER!(KernelProgramInfo<'a>, |_self, last_ref_id, stream| { 1 }); -DECLARE_FIXED_LENGTH_CLUSTER!(UnlinkedCall, |_self, last_ref_id, stream| { 1 }); -DECLARE_FIXED_LENGTH_CLUSTER!(ICData, |_self, last_ref_id, stream| { 1 }); -DECLARE_FIXED_LENGTH_CLUSTER!(MegamorphicCache, |_self, last_ref_id, stream| { 1 }); -DECLARE_FIXED_LENGTH_CLUSTER!(SubtypeTestCache, |_self, last_ref_id, stream| { 1 }); -DECLARE_FIXED_LENGTH_CLUSTER!(LoadingUnit<'a>, |_self, last_ref_id, stream| { 1 }); -DECLARE_FIXED_LENGTH_CLUSTER!(LanguageError, |_self, last_ref_id, stream| { 1 }); -DECLARE_FIXED_LENGTH_CLUSTER!(UnhandledException, |_self, last_ref_id, stream| { 1 }); -DECLARE_FIXED_LENGTH_CLUSTER!(LibraryPrefix, |_self, last_ref_id, stream| { 1 }); -DECLARE_FIXED_LENGTH_CLUSTER!(Type<'a>, |_self, last_ref_id, stream| { 1 }); -DECLARE_FIXED_LENGTH_CLUSTER!(FunctionType<'a>, |_self, last_ref_id, stream| { 1 }); -DECLARE_FIXED_LENGTH_CLUSTER!(RecordType, |_self, last_ref_id, stream| { 1 }); -DECLARE_FIXED_LENGTH_CLUSTER!(TypeParameter<'a>, |_self, last_ref_id, stream| { 1 }); -DECLARE_FIXED_LENGTH_CLUSTER!(Closure<'a>, |_self, last_ref_id, stream| { 1 }); -DECLARE_FIXED_LENGTH_CLUSTER!(Double, |_self, last_ref_id, stream| { 1 }); -DECLARE_FIXED_LENGTH_CLUSTER!(Int32x4, |_self, last_ref_id, stream| { 1 }); -DECLARE_FIXED_LENGTH_CLUSTER!(GrowableObjectArray<'a>, |_self, last_ref_id, stream| { 1 }); -DECLARE_FIXED_LENGTH_CLUSTER!(TypedDataView<'a>, |_self, last_ref_id, stream| { 1 }); -DECLARE_FIXED_LENGTH_CLUSTER!(ExternalTypedData, |_self, last_ref_id, stream| { 1 }); -DECLARE_FIXED_LENGTH_CLUSTER!(StackTrace, |_self, last_ref_id, stream| { 1 }); -DECLARE_FIXED_LENGTH_CLUSTER!(RegExp, |_self, last_ref_id, stream| { 1 }); -DECLARE_FIXED_LENGTH_CLUSTER!(WeakProperty, |_self, last_ref_id, stream| { 1 }); +DECLARE_FIXED_LENGTH_CLUSTER!(TypeParameters, |_self, last_ref_id, stream| { + for obj_idx in 0.._self.obj_count as usize { + let obj = &mut *_self.objs[obj_idx]; + obj.names = stream.read_ref_id(); + obj.flags = stream.read_ref_id(); + obj.bounds = stream.read_ref_id(); + obj.defaults = stream.read_ref_id(); + } +}); +DECLARE_FIXED_LENGTH_CLUSTER!(PatchClass, |_self, last_ref_id, stream| { + for obj_idx in 0.._self.obj_count as usize { + let obj = &mut *_self.objs[obj_idx]; + obj.wrapped_class = stream.read_ref_id(); + obj.script = stream.read_ref_id(); + obj.kernel_program_info = stream.read_ref_id(); + } +}); +DECLARE_FIXED_LENGTH_CLUSTER!(Function, |_self, last_ref_id, stream| { + for obj_idx in 0.._self.obj_count as usize { + let obj = &mut *_self.objs[obj_idx]; + obj.name = stream.read_ref_id(); + obj.owner = stream.read_ref_id(); + obj.signature = stream.read_ref_id(); + obj.data = stream.read_ref_id(); + obj.ic_data_array_or_bytecode = stream.read_ref_id(); + obj.code = stream.read_ref_id(); + obj.positional_parameter_names = stream.read_ref_id(); + obj.unoptimized_code = stream.read_ref_id(); + obj.bitmap = stream.read_modified_leb128(UNSIGNED_M) as u64; + obj.kernel_offset = stream.read_modified_leb128(UNSIGNED_M) as u32; + obj.kind_tag = stream.read_modified_leb128(UNSIGNED_M) as u32; + } +}); +DECLARE_FIXED_LENGTH_CLUSTER!(ClosureData, |_self, last_ref_id, stream| { + for obj_idx in 0.._self.obj_count as usize { + let obj = &mut *_self.objs[obj_idx]; + obj.context_scope = stream.read_ref_id(); + obj.parent_function = stream.read_ref_id(); + obj.closure = stream.read_ref_id(); + obj.packed_fields = stream.read_modified_leb128(UNSIGNED_M) as u32; + } +}); +DECLARE_FIXED_LENGTH_CLUSTER!(FfiTrampolineData, |_self, last_ref_id, stream| { + for obj_idx in 0.._self.obj_count as usize { + let obj = &mut *_self.objs[obj_idx]; + obj.signature_type = stream.read_ref_id(); + obj.c_signature = stream.read_ref_id(); + obj.callback_target = stream.read_ref_id(); + obj.callback_exceptional_return = stream.read_ref_id(); + obj.ffi_function_kind = stream.read_modified_leb128(UNSIGNED_M) as u8; + obj.callback_id = stream.read_modified_leb128(SIGNED_M) as i32; + } +}); +DECLARE_FIXED_LENGTH_CLUSTER!(Field, |_self, last_ref_id, stream| { + for obj_idx in 0.._self.obj_count as usize { + let obj = &mut *_self.objs[obj_idx]; + obj.name = stream.read_ref_id(); + obj.owner = stream.read_ref_id(); + obj.type_field = stream.read_ref_id(); + obj.initializer_function = stream.read_ref_id(); + obj.host_offset_or_field_id = stream.read_ref_id(); + obj.guarded_list_length = stream.read_ref_id(); + obj.exact_type = stream.read_ref_id(); + obj.dependent_code = stream.read_ref_id(); + obj.kernel_offset = stream.read_modified_leb128(UNSIGNED_M) as u32; + obj.guarded_list_length_in_object_offset = stream.read_modified_leb128(SIGNED_M) as i8; + obj.static_type_exactness_state = stream.read_modified_leb128(SIGNED_M) as i8; + obj.target_offset = stream.read_modified_leb128(SIGNED_M) as i32; + obj.kind_bits = stream.read_modified_leb128(UNSIGNED_M) as u32; + } +}); +DECLARE_FIXED_LENGTH_CLUSTER!(Script, |_self, last_ref_id, stream| { + for _ in 0.._self.obj_count as usize {} +}); +DECLARE_FIXED_LENGTH_CLUSTER!(Library, |_self, last_ref_id, stream| { + for obj_idx in 0.._self.obj_count as usize { + let obj = &mut *_self.objs[obj_idx]; + obj.name = stream.read_ref_id(); + obj.url = stream.read_ref_id(); + obj.private_key = stream.read_ref_id(); + obj.dictionary = stream.read_ref_id(); + obj.metadata = stream.read_ref_id(); + obj.toplevel_class = stream.read_ref_id(); + obj.used_scripts = stream.read_ref_id(); + obj.loading_unit = stream.read_ref_id(); + obj.imports = stream.read_ref_id(); + obj.exports = stream.read_ref_id(); + obj.dependencies = stream.read_ref_id(); + obj.kernel_program_info = stream.read_ref_id(); + obj.loaded_scripts = stream.read_ref_id(); + obj.num_imports = stream.read_modified_leb128(UNSIGNED_M) as u16; + obj.flags = stream.read_modified_leb128(UNSIGNED_M) as u8; + obj.kernel_library_index = stream.read_modified_leb128(UNSIGNED_M) as u32; + obj.load_state = stream.read_modified_leb128(SIGNED_M) as i8; + } +}); +DECLARE_FIXED_LENGTH_CLUSTER!(Namespace, |_self, last_ref_id, stream| { + for obj_idx in 0.._self.obj_count as usize { + let obj = &mut *_self.objs[obj_idx]; + obj.target = stream.read_ref_id(); + obj.show_names = stream.read_ref_id(); + obj.hide_names = stream.read_ref_id(); + obj.owner = stream.read_ref_id(); + } +}); +DECLARE_FIXED_LENGTH_CLUSTER!(KernelProgramInfo, |_self, last_ref_id, stream| { + for obj_idx in 0.._self.obj_count as usize { + let obj = &mut *_self.objs[obj_idx]; + obj.kernel_component = stream.read_ref_id(); + obj.string_offsets = stream.read_ref_id(); + obj.string_data = stream.read_ref_id(); + obj.canonical_names = stream.read_ref_id(); + obj.metadata_payloads = stream.read_ref_id(); + obj.metadata_mappings = stream.read_ref_id(); + obj.scripts = stream.read_ref_id(); + obj.constants = stream.read_ref_id(); + obj.constants_table = stream.read_ref_id(); + obj.libraries_cache = stream.read_ref_id(); + obj.classes_cache = stream.read_ref_id(); + } +}); +DECLARE_FIXED_LENGTH_CLUSTER!(UnlinkedCall, |_self, last_ref_id, stream| { + for obj_idx in 0.._self.obj_count as usize { + let obj = &mut *_self.objs[obj_idx]; + obj.can_patch_to_monomorphic = stream.read_modified_leb128(UNSIGNED_M) != 0; + } +}); +DECLARE_FIXED_LENGTH_CLUSTER!(ICData, |_self, last_ref_id, stream| { + for obj_idx in 0.._self.obj_count as usize { + let obj = &mut *_self.objs[obj_idx]; + obj.state_bits = stream.read_modified_leb128(UNSIGNED_M) as u32; + } +}); +DECLARE_FIXED_LENGTH_CLUSTER!(MegamorphicCache, |_self, last_ref_id, stream| { + for obj_idx in 0.._self.obj_count as usize { + let obj = &mut *_self.objs[obj_idx]; + obj.filled_entry_count = stream.read_modified_leb128(SIGNED_M) as i32; + } +}); +DECLARE_FIXED_LENGTH_CLUSTER!(SubtypeTestCache, |_self, last_ref_id, stream| { + for obj_idx in 0.._self.obj_count as usize { + let obj = &mut *_self.objs[obj_idx]; + obj.num_inputs = stream.read_modified_leb128(UNSIGNED_M) as u32; + obj.num_occupied = stream.read_modified_leb128(UNSIGNED_M) as u32; + } +}); +DECLARE_FIXED_LENGTH_CLUSTER!(LoadingUnit, |_self, last_ref_id, stream| { + for obj_idx in 0.._self.obj_count as usize { + let obj = &mut *_self.objs[obj_idx]; + obj.parent = stream.read_ref_id(); + obj.base_objects = stream.read_ref_id(); + obj.packed_fields = stream.read_modified_leb128(SIGNED_M) as i64; + } +}); +DECLARE_FIXED_LENGTH_CLUSTER!(LanguageError, |_self, last_ref_id, stream| { + for obj_idx in 0.._self.obj_count as usize { + let obj = &mut *_self.objs[obj_idx]; + obj.previous_error = stream.read_ref_id(); + obj.script = stream.read_ref_id(); + obj.message = stream.read_ref_id(); + obj.formatted_message = stream.read_ref_id(); + obj.kind = stream.read_modified_leb128(SIGNED_M) as i8; + obj.report_after_token = stream.read_modified_leb128(UNSIGNED_M) != 0; + } +}); +DECLARE_FIXED_LENGTH_CLUSTER!(UnhandledException, |_self, last_ref_id, stream| { + for obj_idx in 0.._self.obj_count as usize { + let obj = &mut *_self.objs[obj_idx]; + obj.exception = stream.read_ref_id(); + obj.stacktrace = stream.read_ref_id(); + } +}); +DECLARE_FIXED_LENGTH_CLUSTER!(LibraryPrefix, |_self, last_ref_id, stream| { + for obj_idx in 0.._self.obj_count as usize { + let obj = &mut *_self.objs[obj_idx]; + obj.name = stream.read_ref_id(); + obj.imports = stream.read_ref_id(); + obj.importer = stream.read_ref_id(); + obj.num_imports = stream.read_modified_leb128(UNSIGNED_M) as u16; + obj.is_deferred_load = stream.read_modified_leb128(UNSIGNED_M) != 0; + } +}); +DECLARE_FIXED_LENGTH_CLUSTER!(Type, |_self, last_ref_id, stream| { + for obj_idx in 0.._self.obj_count as usize { + let obj = &mut *_self.objs[obj_idx]; + obj.arguments = stream.read_ref_id(); + } +}); +DECLARE_FIXED_LENGTH_CLUSTER!(FunctionType, |_self, last_ref_id, stream| { + for obj_idx in 0.._self.obj_count as usize { + let obj = &mut *_self.objs[obj_idx]; + obj.type_parameters = stream.read_ref_id(); + obj.result_type = stream.read_ref_id(); + obj.parameter_types = stream.read_ref_id(); + obj.named_parameter_names = stream.read_ref_id(); + obj.packed_parameter_counts = stream.read_modified_leb128(UNSIGNED_M) as u32; + obj.packed_type_parameter_counts = stream.read_modified_leb128(UNSIGNED_M) as u16; + } +}); +DECLARE_FIXED_LENGTH_CLUSTER!(RecordType, |_self, last_ref_id, stream| { + for obj_idx in 0.._self.obj_count as usize { + let obj = &mut *_self.objs[obj_idx]; + obj.field_types = stream.read_ref_id(); + obj.shape = stream.read_ref_id() as i64; + } +}); +DECLARE_FIXED_LENGTH_CLUSTER!(TypeParameter, |_self, last_ref_id, stream| { + for obj_idx in 0.._self.obj_count as usize { + let obj = &mut *_self.objs[obj_idx]; + obj.owner = stream.read_ref_id(); + obj.base = stream.read_modified_leb128(UNSIGNED_M) as u16; + obj.index = stream.read_modified_leb128(UNSIGNED_M) as u16; + } +}); +DECLARE_FIXED_LENGTH_CLUSTER!(Closure, |_self, last_ref_id, stream| { + for obj_idx in 0.._self.obj_count as usize { + let obj = &mut *_self.objs[obj_idx]; + obj.instantiator_type_arguments = stream.read_ref_id(); + obj.function_type_arguments = stream.read_ref_id(); + obj.delayed_type_arguments = stream.read_ref_id(); + obj.function = stream.read_ref_id(); + obj.context = stream.read_ref_id(); + obj.hash = stream.read_ref_id(); + } +}); +DECLARE_FIXED_LENGTH_CLUSTER!(Double, |_self, last_ref_id, stream| { + for obj_idx in 0.._self.obj_count as usize { + let obj = &mut *_self.objs[obj_idx]; + obj.value = f64::from_bits(stream.read_u64()); + } +}); +DECLARE_FIXED_LENGTH_CLUSTER!(Int32x4, |_self, last_ref_id, stream| { + for _ in 0.._self.obj_count as usize {} +}); +DECLARE_FIXED_LENGTH_CLUSTER!(GrowableObjectArray, |_self, last_ref_id, stream| { + for obj_idx in 0.._self.obj_count as usize { + let obj = &mut *_self.objs[obj_idx]; + obj.type_arguments = stream.read_ref_id(); + obj.data = stream.read_ref_id(); + obj.length = stream.read_ref_id() as i64; + } +}); +DECLARE_FIXED_LENGTH_CLUSTER!(TypedDataView, |_self, last_ref_id, stream| { + for obj_idx in 0.._self.obj_count as usize { + let obj = &mut *_self.objs[obj_idx]; + obj.typed_data = stream.read_ref_id(); + obj.offset_in_bytes = stream.read_ref_id() as i64; + } +}); +DECLARE_FIXED_LENGTH_CLUSTER!(ExternalTypedData, |_self, last_ref_id, stream| { + for _ in 0.._self.obj_count as usize {} +}); +DECLARE_FIXED_LENGTH_CLUSTER!(StackTrace, |_self, last_ref_id, stream| { + for obj_idx in 0.._self.obj_count as usize { + let obj = &mut *_self.objs[obj_idx]; + obj.async_link = stream.read_ref_id(); + obj.code_array = stream.read_ref_id(); + obj.pc_offset_array = stream.read_ref_id(); + obj.expand_inlined = stream.read_modified_leb128(UNSIGNED_M) != 0; + } +}); +DECLARE_FIXED_LENGTH_CLUSTER!(RegExp, |_self, last_ref_id, stream| { + for obj_idx in 0.._self.obj_count as usize { + let obj = &mut *_self.objs[obj_idx]; + obj.capture_name_map = stream.read_ref_id(); + obj.pattern = stream.read_ref_id(); + obj.one_byte = stream.read_ref_id(); + obj.two_byte = stream.read_ref_id(); + obj.one_byte_sticky = stream.read_ref_id(); + obj.two_byte_sticky = stream.read_ref_id(); + obj.flags = stream.read_modified_leb128(UNSIGNED_M) as u32; + } +}); +DECLARE_FIXED_LENGTH_CLUSTER!(WeakProperty, |_self, last_ref_id, stream| { + for obj_idx in 0.._self.obj_count as usize { + let obj = &mut *_self.objs[obj_idx]; + obj.key = stream.read_ref_id(); + obj.value = stream.read_ref_id(); + obj.next_seen_by_gc = stream.read_ref_id(); + } +}); DECLARE_VARIABLE_LENGTH_CLUSTER!(Map); DECLARE_VARIABLE_LENGTH_CLUSTER!(Set); -DECLARE_VARIABLE_LENGTH_CLUSTER!(Instance<'a>); -DECLARE_VARIABLE_LENGTH_CLUSTER!(TypedData<'a>); -DECLARE_VARIABLE_LENGTH_CLUSTER!(Class<'a>); -DECLARE_VARIABLE_LENGTH_CLUSTER!(TypeArguments<'a>); -DECLARE_VARIABLE_LENGTH_CLUSTER!(Code<'a>); +DECLARE_VARIABLE_LENGTH_CLUSTER!(Instance); +DECLARE_VARIABLE_LENGTH_CLUSTER!(TypedData); +DECLARE_VARIABLE_LENGTH_CLUSTER!(Class); +DECLARE_VARIABLE_LENGTH_CLUSTER!(TypeArguments); +DECLARE_VARIABLE_LENGTH_CLUSTER!(Code); DECLARE_VARIABLE_LENGTH_CLUSTER!(ObjectPool); -DECLARE_VARIABLE_LENGTH_CLUSTER!(ExceptionHandlers<'a>); -DECLARE_VARIABLE_LENGTH_CLUSTER!(Context<'a>); +DECLARE_VARIABLE_LENGTH_CLUSTER!(ExceptionHandlers); +DECLARE_VARIABLE_LENGTH_CLUSTER!(Context); DECLARE_VARIABLE_LENGTH_CLUSTER!(ContextScope); DECLARE_VARIABLE_LENGTH_CLUSTER!(Mint); DECLARE_VARIABLE_LENGTH_CLUSTER!(Float32x4); DECLARE_VARIABLE_LENGTH_CLUSTER!(Float64x2); DECLARE_VARIABLE_LENGTH_CLUSTER!(Record); -DECLARE_VARIABLE_LENGTH_CLUSTER!(Array<'a>); -DECLARE_VARIABLE_LENGTH_CLUSTER!(WeakArray<'a>); +DECLARE_VARIABLE_LENGTH_CLUSTER!(Array); +DECLARE_VARIABLE_LENGTH_CLUSTER!(WeakArray); DECLARE_VARIABLE_LENGTH_CLUSTER!(ImmutableArray); DECLARE_VARIABLE_LENGTH_CLUSTER!(ConstMap); DECLARE_VARIABLE_LENGTH_CLUSTER!(ConstSet); diff --git a/crates/flutterdec-serwalker/src/raw_object/mod.rs b/crates/flutterdec-serwalker/src/raw_object/mod.rs index 49f0429..9d44ed4 100644 --- a/crates/flutterdec-serwalker/src/raw_object/mod.rs +++ b/crates/flutterdec-serwalker/src/raw_object/mod.rs @@ -1,120 +1,180 @@ -type Smi = i32; // Using i64 for Smi fields to be decompressed +type Smi = i64; -// --- Fixed-Size Objects with defined fields --- +#[derive(Default)] +pub struct Object { + pub tags: u64, +} #[derive(Default)] -pub struct Mint { - pub value: i64, +pub struct Class { + pub name: u32, // StringPtr + pub user_name: u32, // StringPtr + pub functions: u32, // ArrayPtr + pub functions_hash_table: u32, // ArrayPtr + pub fields: u32, // ArrayPtr + pub offset_in_words_to_field: u32, // ArrayPtr + pub interfaces: u32, // ArrayPtr + pub script: u32, // ScriptPtr + pub library: u32, // LibraryPtr + pub type_parameters: u32, // TypeParametersPtr + pub super_type: u32, // TypePtr + pub constants: u32, // ArrayPtr + pub declaration_type: u32, // TypePtr + pub invocation_dispatcher_cache: u32, // ArrayPtr + pub direct_implementors: u32, // GrowableObjectArrayPtr + pub direct_subclasses: u32, // GrowableObjectArrayPtr + pub declaration_instance_type_arguments: u32, // TypeArgumentsPtr + pub allocation_stub: u32, // CodePtr + pub dependent_code: u32, // WeakArrayPtr + pub num_native_fields: u16, + pub state_bits: u32, + pub kernel_offset: u32, + pub num_type_arguments: i16, + pub host_instance_size_in_words: i32, + pub host_type_arguments_field_offset_in_words: i32, + pub host_next_field_offset_in_words: i32, + pub target_instance_size_in_words: i32, + pub target_type_arguments_field_offset_in_words: i32, + pub target_next_field_offset_in_words: i32, } #[derive(Default)] -pub struct Double { - pub value: f64, +pub struct PatchClass { + pub wrapped_class: u32, // ClassPtr + pub script: u32, // ScriptPtr + pub kernel_program_info: u32, // KernelProgramInfoPtr } #[derive(Default)] -pub struct TypeArguments<'a> { - pub instantiations: Option<&'a mut Array<'a>>, // ArrayPtr - pub length: Smi, // Smi - pub hash: Smi, // Smi - pub nullability: Smi, // Smi +pub struct Function { + pub name: u32, // StringPtr + pub owner: u32, // ObjectPtr + pub signature: u32, // FunctionTypePtr + pub data: u32, // ObjectPtr + pub ic_data_array_or_bytecode: u32, // ObjectPtr + pub code: u32, // CodePtr + pub positional_parameter_names: u32, // ArrayPtr + pub unoptimized_code: u32, // CodePtr + pub bitmap: u64, + pub kernel_offset: u32, + pub kind_tag: u32, } #[derive(Default)] -pub struct TypeParameter<'a> { - pub owner: Option<&'a mut Object<'a>>, // ObjectPtr - pub base: i16, - pub index: i16, +pub struct ClosureData { + pub context_scope: u32, // ContextScopePtr + pub parent_function: u32, // FunctionPtr + pub closure: u32, // ClosurePtr + pub packed_fields: u32, } #[derive(Default)] -pub struct Type<'a> { - pub arguments: Option<&'a mut TypeArguments<'a>>, // TypeArgumentsPtr +pub struct FfiTrampolineData { + pub signature_type: u32, // TypePtr + pub c_signature: u32, // FunctionTypePtr + pub callback_target: u32, // FunctionPtr + pub callback_exceptional_return: u32, // InstancePtr + pub ffi_function_kind: u8, + pub callback_id: i32, } #[derive(Default)] -pub struct TypeParameters<'a> { - pub names: Option<&'a mut Array<'a>>, // ArrayPtr - pub flags: Option<&'a mut Array<'a>>, // ArrayPtr - pub bounds: Option<&'a mut TypeArguments<'a>>, // TypeArgumentsPtr - pub defaults: Option<&'a mut TypeArguments<'a>>, // TypeArgumentsPtr +pub struct Field { + pub name: u32, // StringPtr + pub owner: u32, // ObjectPtr + pub type_field: u32, // AbstractTypePtr + pub initializer_function: u32, // FunctionPtr + pub host_offset_or_field_id: u32, // SmiPtr + pub guarded_list_length: u32, // SmiPtr + pub exact_type: u32, // AbstractTypePtr + pub dependent_code: u32, // WeakArrayPtr + pub kernel_offset: u32, + pub guarded_list_length_in_object_offset: i8, + pub static_type_exactness_state: i8, + pub target_offset: i32, + pub kind_bits: u32, } #[derive(Default)] -pub struct PatchClass<'a> { - pub wrapped_class: Option<&'a mut Class<'a>>, // ClassPtr - pub script: Option<&'a mut Script<'a>>, // ScriptPtr - pub kernel_program_info: Option<&'a mut KernelProgramInfo<'a>>, // KernelProgramInfoPtr +pub struct Script { + // Fieldless class } #[derive(Default)] -pub struct ClosureData<'a> { - pub context_scope: Option<&'a mut ContextScope>, // ContextScopePtr - pub parent_function: Option<&'a mut Function<'a>>, // FunctionPtr - pub closure: Option<&'a mut Closure<'a>>, // ClosurePtr - pub packed_fields: u32, +pub struct Library { + pub name: u32, // StringPtr + pub url: u32, // StringPtr + pub private_key: u32, // StringPtr + pub dictionary: u32, // ArrayPtr + pub metadata: u32, // ArrayPtr + pub toplevel_class: u32, // ClassPtr + pub used_scripts: u32, // GrowableObjectArrayPtr + pub loading_unit: u32, // LoadingUnitPtr + pub imports: u32, // ArrayPtr + pub exports: u32, // ArrayPtr + pub dependencies: u32, // ArrayPtr + pub kernel_program_info: u32, // KernelProgramInfoPtr + pub loaded_scripts: u32, // ArrayPtr + pub num_imports: u16, + pub flags: u8, + pub kernel_library_index: u32, + pub load_state: i8, } #[derive(Default)] -pub struct FfiTrampolineData<'a> { - pub signature_type: Option<&'a mut Type<'a>>, // TypePtr - pub c_signature: Option<&'a mut FunctionType<'a>>, // FunctionTypePtr - pub callback_target: Option<&'a mut Function<'a>>, // FunctionPtr - pub callback_exceptional_return: Option<&'a mut Instance<'a>>, // InstancePtr - pub ffi_function_kind: u8, - pub callback_id: i32, +pub struct Namespace { + pub target: u32, // LibraryPtr + pub show_names: u32, // ArrayPtr + pub hide_names: u32, // ArrayPtr + pub owner: u32, // LibraryPtr } #[derive(Default)] -pub struct Field<'a> { - pub name: _String, // StringPtr -> Raw String - pub owner: Option<&'a mut Object<'a>>, // ObjectPtr - pub type_field: Option<&'a mut AbstractType<'a>>, // AbstractTypePtr - pub initializer_function: Option<&'a mut Function<'a>>, // FunctionPtr - pub host_offset_or_field_id: Smi, // Smi - pub guarded_list_length: Smi, // Smi - pub exact_type: Option<&'a mut AbstractType<'a>>, // AbstractTypePtr - pub dependent_code: Option<&'a mut WeakArray<'a>>, // WeakArrayPtr - pub kernel_offset: i32, - pub guarded_list_length_in_object_offset: i8, - pub static_type_exactness_state: i8, - pub target_offset: i32, - pub kind_bits: u32, +pub struct KernelProgramInfo { + pub kernel_component: u32, // TypedDataBasePtr + pub string_offsets: u32, // TypedDataPtr + pub string_data: u32, // TypedDataViewPtr + pub canonical_names: u32, // TypedDataPtr + pub metadata_payloads: u32, // TypedDataViewPtr + pub metadata_mappings: u32, // TypedDataViewPtr + pub scripts: u32, // ArrayPtr + pub constants: u32, // ArrayPtr + pub constants_table: u32, // TypedDataViewPtr + pub libraries_cache: u32, // ArrayPtr + pub classes_cache: u32, // ArrayPtr +} + +#[derive(Default)] +pub struct CodeSourceMap { + // Fieldless class } #[derive(Default)] -pub struct Namespace<'a> { - pub target: Option<&'a mut Library<'a>>, // LibraryPtr - pub show_names: Option<&'a mut Array<'a>>, // ArrayPtr - pub hide_names: Option<&'a mut Array<'a>>, // ArrayPtr - pub owner: Option<&'a mut Library<'a>>, // LibraryPtr +pub struct CompressedStackMaps { + // Fieldless class } #[derive(Default)] -pub struct KernelProgramInfo<'a> { - pub kernel_component: Option<&'a mut TypedDataBase<'a>>, // TypedDataBasePtr - pub string_offsets: Option<&'a mut TypedData<'a>>, // TypedDataPtr - pub string_data: Option<&'a mut TypedDataView<'a>>, // TypedDataViewPtr - pub canonical_names: Option<&'a mut TypedData<'a>>, // TypedDataPtr - pub metadata_payloads: Option<&'a mut TypedDataView<'a>>, // TypedDataViewPtr - pub metadata_mappings: Option<&'a mut TypedDataView<'a>>, // TypedDataViewPtr - pub scripts: Option<&'a mut Array<'a>>, // ArrayPtr - pub constants: Option<&'a mut Array<'a>>, // ArrayPtr - pub constants_table: Option<&'a mut TypedDataView<'a>>, // TypedDataViewPtr - pub libraries_cache: Option<&'a mut Array<'a>>, // ArrayPtr - pub classes_cache: Option<&'a mut Array<'a>>, // ArrayPtr +pub struct PcDescriptors { + // Fieldless class } #[derive(Default)] -pub struct ExceptionHandlers<'a> { - pub handled_types_data: Option<&'a mut Array<'a>>, // ArrayPtr +pub struct ExceptionHandlers { + pub handled_types_data: u32, // ArrayPtr pub packed_fields: u32, } #[derive(Default)] -pub struct Context<'a> { - pub parent: Option<&'a mut Context<'a>>, // ContextPtr +pub struct Context { + pub parent: u32, // ContextPtr + pub num_variables: i32, +} + +#[derive(Default)] +pub struct ContextScope { pub num_variables: i32, + pub is_implicit: bool, } #[derive(Default)] @@ -123,204 +183,265 @@ pub struct UnlinkedCall { } #[derive(Default)] -pub struct _String { - // added underscore so there's no conflict between this type and rust's _String - pub hash: Smi, // Smi - pub length: Smi, // Smi - pub inner_string: String, -} - -#[derive(Default)] -pub struct Class<'a> { - pub name: _String, // StringPtr -> Raw String - pub user_name: _String, // StringPtr -> Raw String - pub functions: Option<&'a mut Array<'a>>, // ArrayPtr - pub functions_hash_table: Option<&'a mut Array<'a>>, // ArrayPtr - pub fields: Option<&'a mut Array<'a>>, // ArrayPtr - pub offset_in_words_to_field: Option<&'a mut Array<'a>>, // ArrayPtr - pub interfaces: Option<&'a mut Array<'a>>, // ArrayPtr - pub script: Option<&'a mut Script<'a>>, // ScriptPtr - pub library: Option<&'a mut Library<'a>>, // LibraryPtr - pub type_parameters: Option<&'a mut TypeParameters<'a>>, // TypeParametersPtr - pub super_type: Option<&'a mut Type<'a>>, // TypePtr - pub constants: Option<&'a mut Array<'a>>, // ArrayPtr - pub declaration_type: Option<&'a mut Type<'a>>, // TypePtr - pub invocation_dispatcher_cache: Option<&'a mut Array<'a>>, // ArrayPtr - pub direct_implementors: Option<&'a mut GrowableObjectArray<'a>>, // GrowableObjectArrayPtr - pub direct_subclasses: Option<&'a mut GrowableObjectArray<'a>>, // GrowableObjectArrayPtr - pub declaration_instance_type_arguments: Option<&'a mut TypeArguments<'a>>, // TypeArgumentsPtr - pub allocation_stub: Option<&'a mut Code<'a>>, // CodePtr - pub dependent_code: Option<&'a mut WeakArray<'a>>, // WeakArrayPtr - pub num_type_arguments: i16, - pub num_native_fields: u16, - pub state_bits: u32, - pub host_instance_size_in_words: i32, - pub host_type_arguments_field_offset_in_words: i32, - pub host_next_field_offset_in_words: i32, - pub target_instance_size_in_words: i32, - pub target_type_arguments_field_offset_in_words: i32, - pub target_next_field_offset_in_words: i32, - pub kernel_offset: i32, +pub struct ObjectPool { + // Fieldless class } #[derive(Default)] -pub struct Function<'a> { - pub name: _String, // StringPtr -> Raw String - pub owner: Option<&'a mut Object<'a>>, // ObjectPtr - pub signature: Option<&'a mut FunctionType<'a>>, // FunctionTypePtr - pub data: Option<&'a mut Object<'a>>, // ObjectPtr - pub ic_data_array_or_bytecode: Option<&'a mut Object<'a>>, // ObjectPtr - pub code: Option<&'a mut Code<'a>>, // CodePtr - pub positional_parameter_names: Option<&'a mut Array<'a>>, // ArrayPtr - pub unoptimized_code: Option<&'a mut Code<'a>>, // CodePtr - pub bitmap: u64, - pub kernel_offset: i32, - pub kind_tag: u32, +pub struct Mint { + pub value: i64, // ALIGN8 } #[derive(Default)] -pub struct Library<'a> { - pub name: _String, // StringPtr -> Raw String - pub url: _String, // StringPtr -> Raw String - pub private_key: _String, // StringPtr -> Raw String - pub dictionary: Option<&'a mut Array<'a>>, // ArrayPtr - pub metadata: Option<&'a mut Array<'a>>, // ArrayPtr - pub toplevel_class: Option<&'a mut Class<'a>>, // ClassPtr - pub used_scripts: Option<&'a mut GrowableObjectArray<'a>>, // GrowableObjectArrayPtr - pub loading_unit: Option<&'a mut LoadingUnit<'a>>, // LoadingUnitPtr - pub imports: Option<&'a mut Array<'a>>, // ArrayPtr - pub exports: Option<&'a mut Array<'a>>, // ArrayPtr - pub dependencies: Option<&'a mut Array<'a>>, // ArrayPtr - pub kernel_program_info: Option<&'a mut KernelProgramInfo<'a>>, // KernelProgramInfoPtr - pub loaded_scripts: Option<&'a mut Array<'a>>, // ArrayPtr - pub num_imports: u16, - pub load_state: i8, - pub flags: u8, - pub kernel_library_index: i32, +pub struct Double { + pub value: f64, // ALIGN8 } #[derive(Default)] -pub struct ContextScope { - pub num_variables: i32, - pub is_implicit: bool, +pub struct TypeArguments { + pub instantiations: u32, // ArrayPtr + pub length: Smi, // SmiPtr + pub hash: Smi, // SmiPtr + pub nullability: Smi, // SmiPtr } -// Fieldless classes -// These classes either have no additional payload fields beyond the standard -// instance headers or their payloads are purely variable-length or dynamically read/overlayed on top of a byte stream - #[derive(Default)] -pub struct CodeSourceMap; +pub struct TypeParameter { + pub owner: u32, // ObjectPtr + pub base: u16, + pub index: u16, +} #[derive(Default)] -pub struct CompressedStackMaps; +pub struct Type { + pub arguments: u32, // TypeArgumentsPtr +} #[derive(Default)] -pub struct PcDescriptors; +pub struct TypeParameters { + pub names: u32, // ArrayPtr + pub flags: u32, // ArrayPtr + pub bounds: u32, // TypeArgumentsPtr + pub defaults: u32, // TypeArgumentsPtr +} #[derive(Default)] -pub struct ObjectPool; +pub struct OneByteString { + // Fieldless class +} -// Placeholder structs for references used above that aren't defined yet -// it wouldn't compile without this, though for now they remain unimplemented... #[derive(Default)] -pub struct Array<'a> { - _marker: std::marker::PhantomData<&'a ()>, +pub struct TwoByteString { + // Fieldless class } + #[derive(Default)] -pub struct Object<'a> { - _marker: std::marker::PhantomData<&'a ()>, +pub struct _String { + pub hash: Smi, // SmiPtr + pub length: Smi, // SmiPtr } + #[derive(Default)] -pub struct AbstractType<'a> { - _marker: std::marker::PhantomData<&'a ()>, +pub struct Array { + pub type_arguments: u32, // TypeArgumentsPtr + pub length: Smi, // SmiPtr } + #[derive(Default)] -pub struct FunctionType<'a> { - _marker: std::marker::PhantomData<&'a ()>, +pub struct AbstractType { + pub type_test_stub: u32, // CodePtr + pub hash: u32, // SmiPtr + pub padding: u32, + pub flags: u32, } + #[derive(Default)] -pub struct Script<'a> { - _marker: std::marker::PhantomData<&'a ()>, +pub struct FunctionType { + pub type_parameters: u32, // TypeParametersPtr + pub result_type: u32, // AbstractTypePtr + pub parameter_types: u32, // ArrayPtr + pub named_parameter_names: u32, // ArrayPtr + pub packed_parameter_counts: u32, + pub packed_type_parameter_counts: u16, } + #[derive(Default)] -pub struct Closure<'a> { - _marker: std::marker::PhantomData<&'a ()>, +pub struct Closure { + pub instantiator_type_arguments: u32, // TypeArgumentsPtr + pub function_type_arguments: u32, // TypeArgumentsPtr + pub delayed_type_arguments: u32, // TypeArgumentsPtr + pub function: u32, // FunctionPtr + pub context: u32, // ObjectPtr + pub hash: u32, // SmiPtr } + #[derive(Default)] -pub struct Instance<'a> { - _marker: std::marker::PhantomData<&'a ()>, +pub struct Instance { + // Fieldless class } + #[derive(Default)] -pub struct WeakArray<'a> { - _marker: std::marker::PhantomData<&'a ()>, +pub struct WeakArray { + pub next_seen_by_gc: u32, // WeakArrayPtr + pub length: Smi, // SmiPtr } + #[derive(Default)] -pub struct TypedDataBase<'a> { - _marker: std::marker::PhantomData<&'a ()>, +pub struct TypedDataBase { + pub length: Smi, // SmiPtr + pub padding: u32, } + #[derive(Default)] -pub struct TypedData<'a> { - _marker: std::marker::PhantomData<&'a ()>, +pub struct TypedData { + // Fieldless class } + #[derive(Default)] -pub struct TypedDataView<'a> { - _marker: std::marker::PhantomData<&'a ()>, +pub struct TypedDataView { + pub typed_data: u32, // TypedDataBasePtr + pub offset_in_bytes: Smi, // SmiPtr } + #[derive(Default)] -pub struct GrowableObjectArray<'a> { - _marker: std::marker::PhantomData<&'a ()>, +pub struct GrowableObjectArray { + pub type_arguments: u32, // TypeArgumentsPtr + pub data: u32, // ArrayPtr + pub length: Smi, // SmiPtr } + #[derive(Default)] -pub struct Code<'a> { - _marker: std::marker::PhantomData<&'a ()>, +pub struct Code { + pub state_bits: i32, } + #[derive(Default)] -pub struct LoadingUnit<'a> { - _marker: std::marker::PhantomData<&'a ()>, +pub struct LoadingUnit { + pub parent: u32, // LoadingUnitPtr + pub base_objects: u32, // ArrayPtr + pub packed_fields: i64, } #[derive(Default)] -pub struct ICData; -#[derive(Default)] -pub struct MegamorphicCache; -#[derive(Default)] -pub struct SubtypeTestCache; +pub struct ICData { + pub state_bits: u32, +} + #[derive(Default)] -pub struct LanguageError; +pub struct MegamorphicCache { + pub filled_entry_count: i32, +} + #[derive(Default)] -pub struct UnhandledException; +pub struct SubtypeTestCache { + pub num_inputs: u32, + pub num_occupied: u32, +} + #[derive(Default)] -pub struct LibraryPrefix; +pub struct LanguageError { + pub previous_error: u32, // ErrorPtr + pub script: u32, // ScriptPtr + pub message: u32, // StringPtr + pub formatted_message: u32, // StringPtr + pub kind: i8, + pub report_after_token: bool, +} + #[derive(Default)] -pub struct RecordType; +pub struct UnhandledException { + pub exception: u32, // InstancePtr + pub stacktrace: u32, // InstancePtr +} + #[derive(Default)] -pub struct Int32x4; +pub struct LibraryPrefix { + pub name: u32, // StringPtr + pub imports: u32, // ArrayPtr + pub importer: u32, // LibraryPtr + pub num_imports: u16, + pub is_deferred_load: bool, +} + #[derive(Default)] -pub struct ExternalTypedData; +pub struct RecordType { + pub field_types: u32, // ArrayPtr + pub shape: Smi, // SmiPtr +} + #[derive(Default)] -pub struct StackTrace; +pub struct Int32x4 { + // Fieldless class +} + #[derive(Default)] -pub struct RegExp; +pub struct ExternalTypedData { + // Fieldless class +} + #[derive(Default)] -pub struct WeakProperty; +pub struct StackTrace { + pub async_link: u32, // StackTracePtr + pub code_array: u32, // ArrayPtr + pub pc_offset_array: u32, // TypedDataPtr + pub expand_inlined: bool, +} + #[derive(Default)] -pub struct Map; +pub struct RegExp { + pub capture_name_map: u32, // ArrayPtr + pub pattern: u32, // StringPtr + pub one_byte: u32, // TypedDataPtr + pub two_byte: u32, // TypedDataPtr + pub one_byte_sticky: u32, // TypedDataPtr + pub two_byte_sticky: u32, // TypedDataPtr + pub flags: u32, +} + #[derive(Default)] -pub struct Set; +pub struct WeakProperty { + pub key: u32, // ObjectPtr + pub value: u32, // ObjectPtr + pub next_seen_by_gc: u32, // WeakPropertyPtr +} + #[derive(Default)] -pub struct Float32x4; +pub struct Map { + // Fieldless class +} + #[derive(Default)] -pub struct Float64x2; +pub struct Set { + // Fieldless class +} + #[derive(Default)] -pub struct ConstMap; +pub struct Float32x4 { + // Fieldless class +} + #[derive(Default)] -pub struct ConstSet; +pub struct Float64x2 { + // Fieldless class +} + #[derive(Default)] -pub struct Record; +pub struct ConstMap { + // Fieldless class +} + #[derive(Default)] -pub struct ImmutableArray; +pub struct ConstSet { + // Fieldless class +} + #[derive(Default)] -pub struct OneByteString; +pub struct Record { + pub shape: Smi, // SmiPtr + pub padding: u32, +} + #[derive(Default)] -pub struct TwoByteString; +pub struct ImmutableArray { + // Fieldless class +} diff --git a/crates/flutterdec-serwalker/src/snapshot.rs b/crates/flutterdec-serwalker/src/snapshot.rs index c4cbe5c..272c4a6 100644 --- a/crates/flutterdec-serwalker/src/snapshot.rs +++ b/crates/flutterdec-serwalker/src/snapshot.rs @@ -112,9 +112,9 @@ impl DataSnapshot { self.start_of_fill_area = stream.get_current_pos(); for cluster_idx in 0..self.num_clusters { let cid = self.cluster_order[cluster_idx as usize]; - let cluster_wrapper = self.clusters.get_mut(&cid); + let cluster_opt = self.clusters.get_mut(&cid); - let cluster = cluster_wrapper.unwrap(); // this should never panic + let cluster = cluster_opt.unwrap(); // this should never panic (*cluster).read_fill(stream); } self.end_of_fill_area = stream.get_current_pos(); diff --git a/crates/flutterdec-serwalker/src/utils.rs b/crates/flutterdec-serwalker/src/utils.rs index 4fe79b1..5eb4ec2 100644 --- a/crates/flutterdec-serwalker/src/utils.rs +++ b/crates/flutterdec-serwalker/src/utils.rs @@ -4,11 +4,12 @@ use crate::constants::ClassId; #[macro_export] macro_rules! DECLARE_FIXED_LENGTH_CLUSTER { - ($name:ident $(<$lt:lifetime>)?, |$_self:ident, $last_ref_id:ident, $stream:ident| $fill_impl:block) => { - ::paste::paste! { // this is ugly, but the language doesn't support identifier concatenation - pub struct [<$name Cluster>] $(<$lt>)? + ($name:ident, |$_self:ident, $last_ref_id:ident, $stream:ident| $fill_impl:block) => { + ::paste::paste! { + pub struct [<$name Cluster>] { tags: u32, + cid: ClassId, obj_count: u64, start_of_fill: usize, @@ -19,21 +20,21 @@ macro_rules! DECLARE_FIXED_LENGTH_CLUSTER { first_ref_id: u32, - objs: Vec)? >> // a pair (ref_id, object) + objs: Vec> } - impl $(<$lt>)? Cluster for [<$name Cluster>] $(<$lt>)? // optional lifetime parameter + impl Cluster for [<$name Cluster>] { - fn read_alloc(&mut self, last_ref_id: &mut u64, stream: &mut Stream) -> usize // read tags and count + fn read_alloc(&mut self, last_ref_id: &mut u64, stream: &mut Stream) -> usize { self.start_of_alloc = stream.get_current_pos(); - self.first_ref_id = *last_ref_id as u32; // later used to index the objs vector + self.first_ref_id = *last_ref_id as u32; self.obj_count = stream.read_modified_leb128(UNSIGNED_M); for _obj_idx in 0..self.obj_count { - self.objs.push(Box::<$name $(<$lt>)? >::default()); + self.objs.push(Box::<$name >::default()); } *last_ref_id = *last_ref_id + self.obj_count; @@ -49,10 +50,11 @@ macro_rules! DECLARE_FIXED_LENGTH_CLUSTER { let $_self = self; let $stream = stream; - let fill_size = $fill_impl; + $fill_impl; $_self.end_of_fill = $stream.get_current_pos(); - fill_size + + $_self.end_of_fill - $_self.start_of_fill } fn is_fixed_len(&self) -> bool @@ -67,11 +69,12 @@ macro_rules! DECLARE_FIXED_LENGTH_CLUSTER { #[macro_export] macro_rules! DECLARE_VARIABLE_LENGTH_CLUSTER { - ($name:ident $(<$lt:lifetime>)?) => { + ($name:ident) => { ::paste::paste! { - pub struct [<$name Cluster>] $(<$lt>)? + pub struct [<$name Cluster>] { tags: u32, + cid: ClassId, obj_count: u64, start_of_fill: usize, @@ -82,7 +85,7 @@ macro_rules! DECLARE_VARIABLE_LENGTH_CLUSTER { first_ref_id: u32, - objs: Vec)? >> // a pair (ref_id, object) + objs: Vec> } } } @@ -90,7 +93,7 @@ macro_rules! DECLARE_VARIABLE_LENGTH_CLUSTER { pub struct DecodedTags { class_id: ClassId, - is_deeply_immutable: bool, // we don't really care about this for now, at least + is_deeply_immutable: bool, is_canonical: bool, } From ba5f84c5c3451c9a33cf51a041b819de28ce0c43 Mon Sep 17 00:00:00 2001 From: kalixtez Date: Sat, 30 May 2026 22:03:49 -0500 Subject: [PATCH 24/50] feat(serwalker): added fill and alloc for the _StringCluster class, implemented little read_byte (it was convenient) and modified _String so it suits our purposes --- .../flutterdec-serwalker/src/cluster/mod.rs | 84 ++++++- .../src/raw_object/mod.rs | 219 ++++++++++-------- crates/flutterdec-serwalker/src/stream.rs | 11 + crates/flutterdec-serwalker/src/utils.rs | 6 +- 4 files changed, 209 insertions(+), 111 deletions(-) diff --git a/crates/flutterdec-serwalker/src/cluster/mod.rs b/crates/flutterdec-serwalker/src/cluster/mod.rs index 9a0dbba..e0392a7 100644 --- a/crates/flutterdec-serwalker/src/cluster/mod.rs +++ b/crates/flutterdec-serwalker/src/cluster/mod.rs @@ -1,3 +1,5 @@ +use std::usize; + use crate::constants::{ClassId, ClassId::*, SIGNED_M, UNSIGNED_M}; use crate::raw_object::*; use crate::stream::Stream; @@ -5,8 +7,6 @@ use crate::DECLARE_FIXED_LENGTH_CLUSTER; use crate::DECLARE_VARIABLE_LENGTH_CLUSTER; use crate::FFI_TYPES_LIST; -type Smi = i32; - pub trait Cluster { fn is_fixed_len(&self) -> bool; fn read_alloc(&mut self, last_ref_id: &mut u64, stream: &mut Stream) -> usize; @@ -241,7 +241,7 @@ DECLARE_FIXED_LENGTH_CLUSTER!(RecordType, |_self, last_ref_id, stream| { for obj_idx in 0.._self.obj_count as usize { let obj = &mut *_self.objs[obj_idx]; obj.field_types = stream.read_ref_id(); - obj.shape = stream.read_ref_id() as i64; + obj.shape = stream.read_ref_id() as i32; } }); DECLARE_FIXED_LENGTH_CLUSTER!(TypeParameter, |_self, last_ref_id, stream| { @@ -277,14 +277,14 @@ DECLARE_FIXED_LENGTH_CLUSTER!(GrowableObjectArray, |_self, last_ref_id, stream| let obj = &mut *_self.objs[obj_idx]; obj.type_arguments = stream.read_ref_id(); obj.data = stream.read_ref_id(); - obj.length = stream.read_ref_id() as i64; + obj.length = stream.read_ref_id() as i32; } }); DECLARE_FIXED_LENGTH_CLUSTER!(TypedDataView, |_self, last_ref_id, stream| { for obj_idx in 0.._self.obj_count as usize { let obj = &mut *_self.objs[obj_idx]; obj.typed_data = stream.read_ref_id(); - obj.offset_in_bytes = stream.read_ref_id() as i64; + obj.offset_in_bytes = stream.read_ref_id() as i32; } }); DECLARE_FIXED_LENGTH_CLUSTER!(ExternalTypedData, |_self, last_ref_id, stream| { @@ -343,6 +343,76 @@ DECLARE_VARIABLE_LENGTH_CLUSTER!(ConstSet); DECLARE_VARIABLE_LENGTH_CLUSTER!(CodeSourceMap); DECLARE_VARIABLE_LENGTH_CLUSTER!(CompressedStackMaps); DECLARE_VARIABLE_LENGTH_CLUSTER!(PcDescriptors); -DECLARE_VARIABLE_LENGTH_CLUSTER!(OneByteString); -DECLARE_VARIABLE_LENGTH_CLUSTER!(TwoByteString); +//DECLARE_VARIABLE_LENGTH_CLUSTER!(OneByteString); These only exist when NO COMPRESSED_POINTERS +//DECLARE_VARIABLE_LENGTH_CLUSTER!(TwoByteString); DECLARE_VARIABLE_LENGTH_CLUSTER!(_String); + +impl Cluster for _StringCluster { + fn is_fixed_len(&self) -> bool { + false + } + + fn read_alloc(&mut self, last_ref_id: &mut u64, stream: &mut Stream) -> usize { + self.start_of_alloc = stream.get_current_pos(); + self.first_ref_id = *last_ref_id as u32; + + self.obj_count = stream.read_modified_leb128(UNSIGNED_M); + + for _obj_idx in 0..self.obj_count { + let mut obj = Box::<_String>::default(); + let encoded = stream.read_modified_leb128(UNSIGNED_M); + obj.string_type = if encoded & 1 == 1 { + StrType::TwoByte + } else { + StrType::OneByte + }; + + obj.length = (encoded >> 1) as i32; + self.objs.push(obj); + } + + *last_ref_id = *last_ref_id + self.obj_count; + self.end_of_alloc = stream.get_current_pos(); + + self.end_of_alloc - self.start_of_alloc + } + + fn read_fill(&mut self, stream: &mut Stream) -> usize { + self.start_of_fill = stream.get_current_pos(); + + for obj_idx in 0..self.obj_count { + let encoded = stream.read_modified_leb128(UNSIGNED_M); // why is this here twice? Huh? + let obj = self.objs.get_mut(obj_idx as usize); + + let obj = + obj.unwrap_or_else(|| panic!("Couldn't unwrap... No string at index {obj_idx}")); + + match obj.string_type { + StrType::OneByte => { + obj.internal_str = stream.read_string( + usize::try_from(obj.length).expect("Cannot convert Smi to usize."), + ); + } + StrType::TwoByte => { + // a TwoByteString has exactly `length` 16-bit code units + let mut code_units = Vec::with_capacity(obj.length as usize); + + for _ in 0..obj.length { + // read 2 bytes (little-endian) + let b1 = stream.read_byte() as u16; + let b2 = stream.read_byte() as u16; + let code_unit = b1 | (b2 << 8); + + code_units.push(code_unit); + } + + obj.internal_str = String::from_utf16(&code_units) + .expect("Failed to decode TwoByteString UTF-16 payload"); + } + } + } + + self.end_of_fill = stream.get_current_pos(); + self.end_of_fill - self.start_of_fill + } +} diff --git a/crates/flutterdec-serwalker/src/raw_object/mod.rs b/crates/flutterdec-serwalker/src/raw_object/mod.rs index 9d44ed4..1927024 100644 --- a/crates/flutterdec-serwalker/src/raw_object/mod.rs +++ b/crates/flutterdec-serwalker/src/raw_object/mod.rs @@ -1,4 +1,6 @@ -type Smi = i64; +use std::default; + +pub type Smi = i32; #[derive(Default)] pub struct Object { @@ -7,25 +9,25 @@ pub struct Object { #[derive(Default)] pub struct Class { - pub name: u32, // StringPtr - pub user_name: u32, // StringPtr - pub functions: u32, // ArrayPtr - pub functions_hash_table: u32, // ArrayPtr - pub fields: u32, // ArrayPtr - pub offset_in_words_to_field: u32, // ArrayPtr - pub interfaces: u32, // ArrayPtr - pub script: u32, // ScriptPtr - pub library: u32, // LibraryPtr - pub type_parameters: u32, // TypeParametersPtr - pub super_type: u32, // TypePtr - pub constants: u32, // ArrayPtr - pub declaration_type: u32, // TypePtr - pub invocation_dispatcher_cache: u32, // ArrayPtr - pub direct_implementors: u32, // GrowableObjectArrayPtr - pub direct_subclasses: u32, // GrowableObjectArrayPtr + pub name: u32, // StringPtr + pub user_name: u32, // StringPtr + pub functions: u32, // ArrayPtr + pub functions_hash_table: u32, // ArrayPtr + pub fields: u32, // ArrayPtr + pub offset_in_words_to_field: u32, // ArrayPtr + pub interfaces: u32, // ArrayPtr + pub script: u32, // ScriptPtr + pub library: u32, // LibraryPtr + pub type_parameters: u32, // TypeParametersPtr + pub super_type: u32, // TypePtr + pub constants: u32, // ArrayPtr + pub declaration_type: u32, // TypePtr + pub invocation_dispatcher_cache: u32, // ArrayPtr + pub direct_implementors: u32, // GrowableObjectArrayPtr + pub direct_subclasses: u32, // GrowableObjectArrayPtr pub declaration_instance_type_arguments: u32, // TypeArgumentsPtr - pub allocation_stub: u32, // CodePtr - pub dependent_code: u32, // WeakArrayPtr + pub allocation_stub: u32, // CodePtr + pub dependent_code: u32, // WeakArrayPtr pub num_native_fields: u16, pub state_bits: u32, pub kernel_offset: u32, @@ -40,21 +42,21 @@ pub struct Class { #[derive(Default)] pub struct PatchClass { - pub wrapped_class: u32, // ClassPtr - pub script: u32, // ScriptPtr + pub wrapped_class: u32, // ClassPtr + pub script: u32, // ScriptPtr pub kernel_program_info: u32, // KernelProgramInfoPtr } #[derive(Default)] pub struct Function { - pub name: u32, // StringPtr - pub owner: u32, // ObjectPtr - pub signature: u32, // FunctionTypePtr - pub data: u32, // ObjectPtr - pub ic_data_array_or_bytecode: u32, // ObjectPtr - pub code: u32, // CodePtr + pub name: u32, // StringPtr + pub owner: u32, // ObjectPtr + pub signature: u32, // FunctionTypePtr + pub data: u32, // ObjectPtr + pub ic_data_array_or_bytecode: u32, // ObjectPtr + pub code: u32, // CodePtr pub positional_parameter_names: u32, // ArrayPtr - pub unoptimized_code: u32, // CodePtr + pub unoptimized_code: u32, // CodePtr pub bitmap: u64, pub kernel_offset: u32, pub kind_tag: u32, @@ -62,17 +64,17 @@ pub struct Function { #[derive(Default)] pub struct ClosureData { - pub context_scope: u32, // ContextScopePtr + pub context_scope: u32, // ContextScopePtr pub parent_function: u32, // FunctionPtr - pub closure: u32, // ClosurePtr + pub closure: u32, // ClosurePtr pub packed_fields: u32, } #[derive(Default)] pub struct FfiTrampolineData { - pub signature_type: u32, // TypePtr - pub c_signature: u32, // FunctionTypePtr - pub callback_target: u32, // FunctionPtr + pub signature_type: u32, // TypePtr + pub c_signature: u32, // FunctionTypePtr + pub callback_target: u32, // FunctionPtr pub callback_exceptional_return: u32, // InstancePtr pub ffi_function_kind: u8, pub callback_id: i32, @@ -80,14 +82,14 @@ pub struct FfiTrampolineData { #[derive(Default)] pub struct Field { - pub name: u32, // StringPtr - pub owner: u32, // ObjectPtr - pub type_field: u32, // AbstractTypePtr - pub initializer_function: u32, // FunctionPtr + pub name: u32, // StringPtr + pub owner: u32, // ObjectPtr + pub type_field: u32, // AbstractTypePtr + pub initializer_function: u32, // FunctionPtr pub host_offset_or_field_id: u32, // SmiPtr - pub guarded_list_length: u32, // SmiPtr - pub exact_type: u32, // AbstractTypePtr - pub dependent_code: u32, // WeakArrayPtr + pub guarded_list_length: u32, // SmiPtr + pub exact_type: u32, // AbstractTypePtr + pub dependent_code: u32, // WeakArrayPtr pub kernel_offset: u32, pub guarded_list_length_in_object_offset: i8, pub static_type_exactness_state: i8, @@ -102,19 +104,19 @@ pub struct Script { #[derive(Default)] pub struct Library { - pub name: u32, // StringPtr - pub url: u32, // StringPtr - pub private_key: u32, // StringPtr - pub dictionary: u32, // ArrayPtr - pub metadata: u32, // ArrayPtr - pub toplevel_class: u32, // ClassPtr - pub used_scripts: u32, // GrowableObjectArrayPtr - pub loading_unit: u32, // LoadingUnitPtr - pub imports: u32, // ArrayPtr - pub exports: u32, // ArrayPtr - pub dependencies: u32, // ArrayPtr + pub name: u32, // StringPtr + pub url: u32, // StringPtr + pub private_key: u32, // StringPtr + pub dictionary: u32, // ArrayPtr + pub metadata: u32, // ArrayPtr + pub toplevel_class: u32, // ClassPtr + pub used_scripts: u32, // GrowableObjectArrayPtr + pub loading_unit: u32, // LoadingUnitPtr + pub imports: u32, // ArrayPtr + pub exports: u32, // ArrayPtr + pub dependencies: u32, // ArrayPtr pub kernel_program_info: u32, // KernelProgramInfoPtr - pub loaded_scripts: u32, // ArrayPtr + pub loaded_scripts: u32, // ArrayPtr pub num_imports: u16, pub flags: u8, pub kernel_library_index: u32, @@ -123,25 +125,25 @@ pub struct Library { #[derive(Default)] pub struct Namespace { - pub target: u32, // LibraryPtr + pub target: u32, // LibraryPtr pub show_names: u32, // ArrayPtr pub hide_names: u32, // ArrayPtr - pub owner: u32, // LibraryPtr + pub owner: u32, // LibraryPtr } #[derive(Default)] pub struct KernelProgramInfo { - pub kernel_component: u32, // TypedDataBasePtr - pub string_offsets: u32, // TypedDataPtr - pub string_data: u32, // TypedDataViewPtr - pub canonical_names: u32, // TypedDataPtr + pub kernel_component: u32, // TypedDataBasePtr + pub string_offsets: u32, // TypedDataPtr + pub string_data: u32, // TypedDataViewPtr + pub canonical_names: u32, // TypedDataPtr pub metadata_payloads: u32, // TypedDataViewPtr pub metadata_mappings: u32, // TypedDataViewPtr - pub scripts: u32, // ArrayPtr - pub constants: u32, // ArrayPtr - pub constants_table: u32, // TypedDataViewPtr - pub libraries_cache: u32, // ArrayPtr - pub classes_cache: u32, // ArrayPtr + pub scripts: u32, // ArrayPtr + pub constants: u32, // ArrayPtr + pub constants_table: u32, // TypedDataViewPtr + pub libraries_cache: u32, // ArrayPtr + pub classes_cache: u32, // ArrayPtr } #[derive(Default)] @@ -200,9 +202,9 @@ pub struct Double { #[derive(Default)] pub struct TypeArguments { pub instantiations: u32, // ArrayPtr - pub length: Smi, // SmiPtr - pub hash: Smi, // SmiPtr - pub nullability: Smi, // SmiPtr + pub length: Smi, // SmiPtr + pub hash: Smi, // SmiPtr + pub nullability: Smi, // SmiPtr } #[derive(Default)] @@ -219,12 +221,17 @@ pub struct Type { #[derive(Default)] pub struct TypeParameters { - pub names: u32, // ArrayPtr - pub flags: u32, // ArrayPtr - pub bounds: u32, // TypeArgumentsPtr + pub names: u32, // ArrayPtr + pub flags: u32, // ArrayPtr + pub bounds: u32, // TypeArgumentsPtr pub defaults: u32, // TypeArgumentsPtr } +/* + No need to make two separate structs here. Better to just + have the _String class and add an enum field to determine + the number of bytes "StrType". + #[derive(Default)] pub struct OneByteString { // Fieldless class @@ -234,32 +241,42 @@ pub struct OneByteString { pub struct TwoByteString { // Fieldless class } +*/ + +#[derive(Default)] +pub enum StrType { + #[default] + OneByte, // assume the string is a one byte string. + TwoByte, +} #[derive(Default)] pub struct _String { - pub hash: Smi, // SmiPtr + pub string_type: StrType, + pub hash: Smi, // SmiPtr pub length: Smi, // SmiPtr + pub internal_str: String, } #[derive(Default)] pub struct Array { pub type_arguments: u32, // TypeArgumentsPtr - pub length: Smi, // SmiPtr + pub length: Smi, // SmiPtr } #[derive(Default)] pub struct AbstractType { pub type_test_stub: u32, // CodePtr - pub hash: u32, // SmiPtr + pub hash: u32, // SmiPtr pub padding: u32, pub flags: u32, } #[derive(Default)] pub struct FunctionType { - pub type_parameters: u32, // TypeParametersPtr - pub result_type: u32, // AbstractTypePtr - pub parameter_types: u32, // ArrayPtr + pub type_parameters: u32, // TypeParametersPtr + pub result_type: u32, // AbstractTypePtr + pub parameter_types: u32, // ArrayPtr pub named_parameter_names: u32, // ArrayPtr pub packed_parameter_counts: u32, pub packed_type_parameter_counts: u16, @@ -268,11 +285,11 @@ pub struct FunctionType { #[derive(Default)] pub struct Closure { pub instantiator_type_arguments: u32, // TypeArgumentsPtr - pub function_type_arguments: u32, // TypeArgumentsPtr - pub delayed_type_arguments: u32, // TypeArgumentsPtr - pub function: u32, // FunctionPtr - pub context: u32, // ObjectPtr - pub hash: u32, // SmiPtr + pub function_type_arguments: u32, // TypeArgumentsPtr + pub delayed_type_arguments: u32, // TypeArgumentsPtr + pub function: u32, // FunctionPtr + pub context: u32, // ObjectPtr + pub hash: u32, // SmiPtr } #[derive(Default)] @@ -283,7 +300,7 @@ pub struct Instance { #[derive(Default)] pub struct WeakArray { pub next_seen_by_gc: u32, // WeakArrayPtr - pub length: Smi, // SmiPtr + pub length: Smi, // SmiPtr } #[derive(Default)] @@ -299,15 +316,15 @@ pub struct TypedData { #[derive(Default)] pub struct TypedDataView { - pub typed_data: u32, // TypedDataBasePtr + pub typed_data: u32, // TypedDataBasePtr pub offset_in_bytes: Smi, // SmiPtr } #[derive(Default)] pub struct GrowableObjectArray { pub type_arguments: u32, // TypeArgumentsPtr - pub data: u32, // ArrayPtr - pub length: Smi, // SmiPtr + pub data: u32, // ArrayPtr + pub length: Smi, // SmiPtr } #[derive(Default)] @@ -317,7 +334,7 @@ pub struct Code { #[derive(Default)] pub struct LoadingUnit { - pub parent: u32, // LoadingUnitPtr + pub parent: u32, // LoadingUnitPtr pub base_objects: u32, // ArrayPtr pub packed_fields: i64, } @@ -340,9 +357,9 @@ pub struct SubtypeTestCache { #[derive(Default)] pub struct LanguageError { - pub previous_error: u32, // ErrorPtr - pub script: u32, // ScriptPtr - pub message: u32, // StringPtr + pub previous_error: u32, // ErrorPtr + pub script: u32, // ScriptPtr + pub message: u32, // StringPtr pub formatted_message: u32, // StringPtr pub kind: i8, pub report_after_token: bool, @@ -350,14 +367,14 @@ pub struct LanguageError { #[derive(Default)] pub struct UnhandledException { - pub exception: u32, // InstancePtr + pub exception: u32, // InstancePtr pub stacktrace: u32, // InstancePtr } #[derive(Default)] pub struct LibraryPrefix { - pub name: u32, // StringPtr - pub imports: u32, // ArrayPtr + pub name: u32, // StringPtr + pub imports: u32, // ArrayPtr pub importer: u32, // LibraryPtr pub num_imports: u16, pub is_deferred_load: bool, @@ -366,7 +383,7 @@ pub struct LibraryPrefix { #[derive(Default)] pub struct RecordType { pub field_types: u32, // ArrayPtr - pub shape: Smi, // SmiPtr + pub shape: Smi, // SmiPtr } #[derive(Default)] @@ -381,8 +398,8 @@ pub struct ExternalTypedData { #[derive(Default)] pub struct StackTrace { - pub async_link: u32, // StackTracePtr - pub code_array: u32, // ArrayPtr + pub async_link: u32, // StackTracePtr + pub code_array: u32, // ArrayPtr pub pc_offset_array: u32, // TypedDataPtr pub expand_inlined: bool, } @@ -390,18 +407,18 @@ pub struct StackTrace { #[derive(Default)] pub struct RegExp { pub capture_name_map: u32, // ArrayPtr - pub pattern: u32, // StringPtr - pub one_byte: u32, // TypedDataPtr - pub two_byte: u32, // TypedDataPtr - pub one_byte_sticky: u32, // TypedDataPtr - pub two_byte_sticky: u32, // TypedDataPtr + pub pattern: u32, // StringPtr + pub one_byte: u32, // TypedDataPtr + pub two_byte: u32, // TypedDataPtr + pub one_byte_sticky: u32, // TypedDataPtr + pub two_byte_sticky: u32, // TypedDataPtr pub flags: u32, } #[derive(Default)] pub struct WeakProperty { - pub key: u32, // ObjectPtr - pub value: u32, // ObjectPtr + pub key: u32, // ObjectPtr + pub value: u32, // ObjectPtr pub next_seen_by_gc: u32, // WeakPropertyPtr } diff --git a/crates/flutterdec-serwalker/src/stream.rs b/crates/flutterdec-serwalker/src/stream.rs index 35681ac..5b319d5 100644 --- a/crates/flutterdec-serwalker/src/stream.rs +++ b/crates/flutterdec-serwalker/src/stream.rs @@ -86,6 +86,17 @@ impl<'a> Stream<'a> { u32::from_le_bytes(converted_slice) } + pub fn read_byte(&mut self) -> u8 { + let u8_size = std::mem::size_of::(); + if self.curr_stream_offset < self.byte_stream.len() { + let byte = self.byte_stream[self.curr_stream_offset]; + self.advance_pos(u8_size); + byte + } else { + panic!("Reading past the end of the snapshot, something went really wrong."); + } + } + /* Panics if it isn't possible to create a stream from the utf-8 representation stored in the byte slice. It shouldn't happen, so the best possible outcome is to assume some diff --git a/crates/flutterdec-serwalker/src/utils.rs b/crates/flutterdec-serwalker/src/utils.rs index 5eb4ec2..ae12218 100644 --- a/crates/flutterdec-serwalker/src/utils.rs +++ b/crates/flutterdec-serwalker/src/utils.rs @@ -53,7 +53,7 @@ macro_rules! DECLARE_FIXED_LENGTH_CLUSTER { $fill_impl; $_self.end_of_fill = $stream.get_current_pos(); - + $_self.end_of_fill - $_self.start_of_fill } @@ -64,7 +64,7 @@ macro_rules! DECLARE_FIXED_LENGTH_CLUSTER { } } - } + }; } #[macro_export] @@ -88,7 +88,7 @@ macro_rules! DECLARE_VARIABLE_LENGTH_CLUSTER { objs: Vec> } } - } + }; } pub struct DecodedTags { From 0bb097425d1d829c7a9e491da006d2aea4d3a38f Mon Sep 17 00:00:00 2001 From: kalixtez Date: Sat, 30 May 2026 22:16:01 -0500 Subject: [PATCH 25/50] refactor(selwarker): removed last_ref_id from lambdas in DECLARE_FIXED_LENGTH_CLUSTER as it was unnecessary --- .../flutterdec-serwalker/src/cluster/mod.rs | 64 +++++++++---------- crates/flutterdec-serwalker/src/utils.rs | 2 +- 2 files changed, 33 insertions(+), 33 deletions(-) diff --git a/crates/flutterdec-serwalker/src/cluster/mod.rs b/crates/flutterdec-serwalker/src/cluster/mod.rs index e0392a7..da324a3 100644 --- a/crates/flutterdec-serwalker/src/cluster/mod.rs +++ b/crates/flutterdec-serwalker/src/cluster/mod.rs @@ -38,7 +38,7 @@ pub fn decide_cluster(class_id: ClassId) -> Result, &'static st // whose fill cluster size is uniquely determined by sizeof(Object) * num_of_objects // and alloc cluster size is tags (MULEB128) + num_of_objects (MULEB128) -DECLARE_FIXED_LENGTH_CLUSTER!(TypeParameters, |_self, last_ref_id, stream| { +DECLARE_FIXED_LENGTH_CLUSTER!(TypeParameters, |_self, stream| { for obj_idx in 0.._self.obj_count as usize { let obj = &mut *_self.objs[obj_idx]; obj.names = stream.read_ref_id(); @@ -47,7 +47,7 @@ DECLARE_FIXED_LENGTH_CLUSTER!(TypeParameters, |_self, last_ref_id, stream| { obj.defaults = stream.read_ref_id(); } }); -DECLARE_FIXED_LENGTH_CLUSTER!(PatchClass, |_self, last_ref_id, stream| { +DECLARE_FIXED_LENGTH_CLUSTER!(PatchClass, |_self, stream| { for obj_idx in 0.._self.obj_count as usize { let obj = &mut *_self.objs[obj_idx]; obj.wrapped_class = stream.read_ref_id(); @@ -55,7 +55,7 @@ DECLARE_FIXED_LENGTH_CLUSTER!(PatchClass, |_self, last_ref_id, stream| { obj.kernel_program_info = stream.read_ref_id(); } }); -DECLARE_FIXED_LENGTH_CLUSTER!(Function, |_self, last_ref_id, stream| { +DECLARE_FIXED_LENGTH_CLUSTER!(Function, |_self, stream| { for obj_idx in 0.._self.obj_count as usize { let obj = &mut *_self.objs[obj_idx]; obj.name = stream.read_ref_id(); @@ -71,7 +71,7 @@ DECLARE_FIXED_LENGTH_CLUSTER!(Function, |_self, last_ref_id, stream| { obj.kind_tag = stream.read_modified_leb128(UNSIGNED_M) as u32; } }); -DECLARE_FIXED_LENGTH_CLUSTER!(ClosureData, |_self, last_ref_id, stream| { +DECLARE_FIXED_LENGTH_CLUSTER!(ClosureData, |_self, stream| { for obj_idx in 0.._self.obj_count as usize { let obj = &mut *_self.objs[obj_idx]; obj.context_scope = stream.read_ref_id(); @@ -80,7 +80,7 @@ DECLARE_FIXED_LENGTH_CLUSTER!(ClosureData, |_self, last_ref_id, stream| { obj.packed_fields = stream.read_modified_leb128(UNSIGNED_M) as u32; } }); -DECLARE_FIXED_LENGTH_CLUSTER!(FfiTrampolineData, |_self, last_ref_id, stream| { +DECLARE_FIXED_LENGTH_CLUSTER!(FfiTrampolineData, |_self, stream| { for obj_idx in 0.._self.obj_count as usize { let obj = &mut *_self.objs[obj_idx]; obj.signature_type = stream.read_ref_id(); @@ -91,7 +91,7 @@ DECLARE_FIXED_LENGTH_CLUSTER!(FfiTrampolineData, |_self, last_ref_id, stream| { obj.callback_id = stream.read_modified_leb128(SIGNED_M) as i32; } }); -DECLARE_FIXED_LENGTH_CLUSTER!(Field, |_self, last_ref_id, stream| { +DECLARE_FIXED_LENGTH_CLUSTER!(Field, |_self, stream| { for obj_idx in 0.._self.obj_count as usize { let obj = &mut *_self.objs[obj_idx]; obj.name = stream.read_ref_id(); @@ -109,10 +109,10 @@ DECLARE_FIXED_LENGTH_CLUSTER!(Field, |_self, last_ref_id, stream| { obj.kind_bits = stream.read_modified_leb128(UNSIGNED_M) as u32; } }); -DECLARE_FIXED_LENGTH_CLUSTER!(Script, |_self, last_ref_id, stream| { +DECLARE_FIXED_LENGTH_CLUSTER!(Script, |_self, stream| { for _ in 0.._self.obj_count as usize {} }); -DECLARE_FIXED_LENGTH_CLUSTER!(Library, |_self, last_ref_id, stream| { +DECLARE_FIXED_LENGTH_CLUSTER!(Library, |_self, stream| { for obj_idx in 0.._self.obj_count as usize { let obj = &mut *_self.objs[obj_idx]; obj.name = stream.read_ref_id(); @@ -134,7 +134,7 @@ DECLARE_FIXED_LENGTH_CLUSTER!(Library, |_self, last_ref_id, stream| { obj.load_state = stream.read_modified_leb128(SIGNED_M) as i8; } }); -DECLARE_FIXED_LENGTH_CLUSTER!(Namespace, |_self, last_ref_id, stream| { +DECLARE_FIXED_LENGTH_CLUSTER!(Namespace, |_self, stream| { for obj_idx in 0.._self.obj_count as usize { let obj = &mut *_self.objs[obj_idx]; obj.target = stream.read_ref_id(); @@ -143,7 +143,7 @@ DECLARE_FIXED_LENGTH_CLUSTER!(Namespace, |_self, last_ref_id, stream| { obj.owner = stream.read_ref_id(); } }); -DECLARE_FIXED_LENGTH_CLUSTER!(KernelProgramInfo, |_self, last_ref_id, stream| { +DECLARE_FIXED_LENGTH_CLUSTER!(KernelProgramInfo, |_self, stream| { for obj_idx in 0.._self.obj_count as usize { let obj = &mut *_self.objs[obj_idx]; obj.kernel_component = stream.read_ref_id(); @@ -159,32 +159,32 @@ DECLARE_FIXED_LENGTH_CLUSTER!(KernelProgramInfo, |_self, last_ref_id, stream| { obj.classes_cache = stream.read_ref_id(); } }); -DECLARE_FIXED_LENGTH_CLUSTER!(UnlinkedCall, |_self, last_ref_id, stream| { +DECLARE_FIXED_LENGTH_CLUSTER!(UnlinkedCall, |_self, stream| { for obj_idx in 0.._self.obj_count as usize { let obj = &mut *_self.objs[obj_idx]; obj.can_patch_to_monomorphic = stream.read_modified_leb128(UNSIGNED_M) != 0; } }); -DECLARE_FIXED_LENGTH_CLUSTER!(ICData, |_self, last_ref_id, stream| { +DECLARE_FIXED_LENGTH_CLUSTER!(ICData, |_self, stream| { for obj_idx in 0.._self.obj_count as usize { let obj = &mut *_self.objs[obj_idx]; obj.state_bits = stream.read_modified_leb128(UNSIGNED_M) as u32; } }); -DECLARE_FIXED_LENGTH_CLUSTER!(MegamorphicCache, |_self, last_ref_id, stream| { +DECLARE_FIXED_LENGTH_CLUSTER!(MegamorphicCache, |_self, stream| { for obj_idx in 0.._self.obj_count as usize { let obj = &mut *_self.objs[obj_idx]; obj.filled_entry_count = stream.read_modified_leb128(SIGNED_M) as i32; } }); -DECLARE_FIXED_LENGTH_CLUSTER!(SubtypeTestCache, |_self, last_ref_id, stream| { +DECLARE_FIXED_LENGTH_CLUSTER!(SubtypeTestCache, |_self, stream| { for obj_idx in 0.._self.obj_count as usize { let obj = &mut *_self.objs[obj_idx]; obj.num_inputs = stream.read_modified_leb128(UNSIGNED_M) as u32; obj.num_occupied = stream.read_modified_leb128(UNSIGNED_M) as u32; } }); -DECLARE_FIXED_LENGTH_CLUSTER!(LoadingUnit, |_self, last_ref_id, stream| { +DECLARE_FIXED_LENGTH_CLUSTER!(LoadingUnit, |_self, stream| { for obj_idx in 0.._self.obj_count as usize { let obj = &mut *_self.objs[obj_idx]; obj.parent = stream.read_ref_id(); @@ -192,7 +192,7 @@ DECLARE_FIXED_LENGTH_CLUSTER!(LoadingUnit, |_self, last_ref_id, stream| { obj.packed_fields = stream.read_modified_leb128(SIGNED_M) as i64; } }); -DECLARE_FIXED_LENGTH_CLUSTER!(LanguageError, |_self, last_ref_id, stream| { +DECLARE_FIXED_LENGTH_CLUSTER!(LanguageError, |_self, stream| { for obj_idx in 0.._self.obj_count as usize { let obj = &mut *_self.objs[obj_idx]; obj.previous_error = stream.read_ref_id(); @@ -203,14 +203,14 @@ DECLARE_FIXED_LENGTH_CLUSTER!(LanguageError, |_self, last_ref_id, stream| { obj.report_after_token = stream.read_modified_leb128(UNSIGNED_M) != 0; } }); -DECLARE_FIXED_LENGTH_CLUSTER!(UnhandledException, |_self, last_ref_id, stream| { +DECLARE_FIXED_LENGTH_CLUSTER!(UnhandledException, |_self, stream| { for obj_idx in 0.._self.obj_count as usize { let obj = &mut *_self.objs[obj_idx]; obj.exception = stream.read_ref_id(); obj.stacktrace = stream.read_ref_id(); } }); -DECLARE_FIXED_LENGTH_CLUSTER!(LibraryPrefix, |_self, last_ref_id, stream| { +DECLARE_FIXED_LENGTH_CLUSTER!(LibraryPrefix, |_self, stream| { for obj_idx in 0.._self.obj_count as usize { let obj = &mut *_self.objs[obj_idx]; obj.name = stream.read_ref_id(); @@ -220,13 +220,13 @@ DECLARE_FIXED_LENGTH_CLUSTER!(LibraryPrefix, |_self, last_ref_id, stream| { obj.is_deferred_load = stream.read_modified_leb128(UNSIGNED_M) != 0; } }); -DECLARE_FIXED_LENGTH_CLUSTER!(Type, |_self, last_ref_id, stream| { +DECLARE_FIXED_LENGTH_CLUSTER!(Type, |_self, stream| { for obj_idx in 0.._self.obj_count as usize { let obj = &mut *_self.objs[obj_idx]; obj.arguments = stream.read_ref_id(); } }); -DECLARE_FIXED_LENGTH_CLUSTER!(FunctionType, |_self, last_ref_id, stream| { +DECLARE_FIXED_LENGTH_CLUSTER!(FunctionType, |_self, stream| { for obj_idx in 0.._self.obj_count as usize { let obj = &mut *_self.objs[obj_idx]; obj.type_parameters = stream.read_ref_id(); @@ -237,14 +237,14 @@ DECLARE_FIXED_LENGTH_CLUSTER!(FunctionType, |_self, last_ref_id, stream| { obj.packed_type_parameter_counts = stream.read_modified_leb128(UNSIGNED_M) as u16; } }); -DECLARE_FIXED_LENGTH_CLUSTER!(RecordType, |_self, last_ref_id, stream| { +DECLARE_FIXED_LENGTH_CLUSTER!(RecordType, |_self, stream| { for obj_idx in 0.._self.obj_count as usize { let obj = &mut *_self.objs[obj_idx]; obj.field_types = stream.read_ref_id(); obj.shape = stream.read_ref_id() as i32; } }); -DECLARE_FIXED_LENGTH_CLUSTER!(TypeParameter, |_self, last_ref_id, stream| { +DECLARE_FIXED_LENGTH_CLUSTER!(TypeParameter, |_self, stream| { for obj_idx in 0.._self.obj_count as usize { let obj = &mut *_self.objs[obj_idx]; obj.owner = stream.read_ref_id(); @@ -252,7 +252,7 @@ DECLARE_FIXED_LENGTH_CLUSTER!(TypeParameter, |_self, last_ref_id, stream| { obj.index = stream.read_modified_leb128(UNSIGNED_M) as u16; } }); -DECLARE_FIXED_LENGTH_CLUSTER!(Closure, |_self, last_ref_id, stream| { +DECLARE_FIXED_LENGTH_CLUSTER!(Closure, |_self, stream| { for obj_idx in 0.._self.obj_count as usize { let obj = &mut *_self.objs[obj_idx]; obj.instantiator_type_arguments = stream.read_ref_id(); @@ -263,16 +263,16 @@ DECLARE_FIXED_LENGTH_CLUSTER!(Closure, |_self, last_ref_id, stream| { obj.hash = stream.read_ref_id(); } }); -DECLARE_FIXED_LENGTH_CLUSTER!(Double, |_self, last_ref_id, stream| { +DECLARE_FIXED_LENGTH_CLUSTER!(Double, |_self, stream| { for obj_idx in 0.._self.obj_count as usize { let obj = &mut *_self.objs[obj_idx]; obj.value = f64::from_bits(stream.read_u64()); } }); -DECLARE_FIXED_LENGTH_CLUSTER!(Int32x4, |_self, last_ref_id, stream| { +DECLARE_FIXED_LENGTH_CLUSTER!(Int32x4, |_self, stream| { for _ in 0.._self.obj_count as usize {} }); -DECLARE_FIXED_LENGTH_CLUSTER!(GrowableObjectArray, |_self, last_ref_id, stream| { +DECLARE_FIXED_LENGTH_CLUSTER!(GrowableObjectArray, |_self, stream| { for obj_idx in 0.._self.obj_count as usize { let obj = &mut *_self.objs[obj_idx]; obj.type_arguments = stream.read_ref_id(); @@ -280,17 +280,17 @@ DECLARE_FIXED_LENGTH_CLUSTER!(GrowableObjectArray, |_self, last_ref_id, stream| obj.length = stream.read_ref_id() as i32; } }); -DECLARE_FIXED_LENGTH_CLUSTER!(TypedDataView, |_self, last_ref_id, stream| { +DECLARE_FIXED_LENGTH_CLUSTER!(TypedDataView, |_self, stream| { for obj_idx in 0.._self.obj_count as usize { let obj = &mut *_self.objs[obj_idx]; obj.typed_data = stream.read_ref_id(); obj.offset_in_bytes = stream.read_ref_id() as i32; } }); -DECLARE_FIXED_LENGTH_CLUSTER!(ExternalTypedData, |_self, last_ref_id, stream| { +DECLARE_FIXED_LENGTH_CLUSTER!(ExternalTypedData, |_self, stream| { for _ in 0.._self.obj_count as usize {} }); -DECLARE_FIXED_LENGTH_CLUSTER!(StackTrace, |_self, last_ref_id, stream| { +DECLARE_FIXED_LENGTH_CLUSTER!(StackTrace, |_self, stream| { for obj_idx in 0.._self.obj_count as usize { let obj = &mut *_self.objs[obj_idx]; obj.async_link = stream.read_ref_id(); @@ -299,7 +299,7 @@ DECLARE_FIXED_LENGTH_CLUSTER!(StackTrace, |_self, last_ref_id, stream| { obj.expand_inlined = stream.read_modified_leb128(UNSIGNED_M) != 0; } }); -DECLARE_FIXED_LENGTH_CLUSTER!(RegExp, |_self, last_ref_id, stream| { +DECLARE_FIXED_LENGTH_CLUSTER!(RegExp, |_self, stream| { for obj_idx in 0.._self.obj_count as usize { let obj = &mut *_self.objs[obj_idx]; obj.capture_name_map = stream.read_ref_id(); @@ -311,7 +311,7 @@ DECLARE_FIXED_LENGTH_CLUSTER!(RegExp, |_self, last_ref_id, stream| { obj.flags = stream.read_modified_leb128(UNSIGNED_M) as u32; } }); -DECLARE_FIXED_LENGTH_CLUSTER!(WeakProperty, |_self, last_ref_id, stream| { +DECLARE_FIXED_LENGTH_CLUSTER!(WeakProperty, |_self, stream| { for obj_idx in 0.._self.obj_count as usize { let obj = &mut *_self.objs[obj_idx]; obj.key = stream.read_ref_id(); @@ -381,7 +381,7 @@ impl Cluster for _StringCluster { self.start_of_fill = stream.get_current_pos(); for obj_idx in 0..self.obj_count { - let encoded = stream.read_modified_leb128(UNSIGNED_M); // why is this here twice? Huh? + let _encoded = stream.read_modified_leb128(UNSIGNED_M); // why is this here twice? Huh? let obj = self.objs.get_mut(obj_idx as usize); let obj = diff --git a/crates/flutterdec-serwalker/src/utils.rs b/crates/flutterdec-serwalker/src/utils.rs index ae12218..cfda42b 100644 --- a/crates/flutterdec-serwalker/src/utils.rs +++ b/crates/flutterdec-serwalker/src/utils.rs @@ -4,7 +4,7 @@ use crate::constants::ClassId; #[macro_export] macro_rules! DECLARE_FIXED_LENGTH_CLUSTER { - ($name:ident, |$_self:ident, $last_ref_id:ident, $stream:ident| $fill_impl:block) => { + ($name:ident, |$_self:ident, $stream:ident| $fill_impl:block) => { ::paste::paste! { pub struct [<$name Cluster>] { From e0ecb112e53509e8f4665178d9d756784e9c3fb6 Mon Sep 17 00:00:00 2001 From: kalixtez Date: Sat, 30 May 2026 22:24:53 -0500 Subject: [PATCH 26/50] fix(serwalker): made it so fetching the class_id from the tag no longer panics... --- crates/flutterdec-serwalker/src/utils.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/flutterdec-serwalker/src/utils.rs b/crates/flutterdec-serwalker/src/utils.rs index cfda42b..b448c7c 100644 --- a/crates/flutterdec-serwalker/src/utils.rs +++ b/crates/flutterdec-serwalker/src/utils.rs @@ -119,7 +119,7 @@ impl DecodedTags { } } -macro_rules! DECODE_IS_CID { +macro_rules! DECODE_CID { ($tags:expr) => { ClassId::try_from(($tags >> 12) & 0xFFFFF) }; @@ -136,7 +136,7 @@ macro_rules! DECODE_IS_CANONICAL { } pub fn decode_tags(tags: u32) -> DecodedTags { - let class_id: ClassId = DECODE_IS_CID!(tags).unwrap(); + let class_id: ClassId = DECODE_CID!(tags).unwrap_or_default(); let is_deeply_immutable: bool = DECODE_IS_DEEPLY_IMMUTABLE!(tags); let is_canonical: bool = DECODE_IS_CANONICAL!(tags); From 2a781b267ead3c3d53625abd8f6ed83fef3a4d94 Mon Sep 17 00:00:00 2001 From: kalixtez Date: Sun, 31 May 2026 12:56:44 -0500 Subject: [PATCH 27/50] fix(serwalker): fixed wrong usage of panic! in snapshot.rs where the cid wouldnt interpolate in the printed error string --- crates/flutterdec-serwalker/src/snapshot.rs | 4 ++-- crates/flutterdec-serwalker/src/stream.rs | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/crates/flutterdec-serwalker/src/snapshot.rs b/crates/flutterdec-serwalker/src/snapshot.rs index 272c4a6..506fcf7 100644 --- a/crates/flutterdec-serwalker/src/snapshot.rs +++ b/crates/flutterdec-serwalker/src/snapshot.rs @@ -12,7 +12,7 @@ enum SnapshotKind Full, FullCore, FullJIT, - FullAOT, // Full + AOT code, this is the one we care about, as th + FullAOT, // Full + AOT code, this is the one we care about, as this is how flutter builds projects Module, #[default] None, @@ -101,7 +101,7 @@ impl DataSnapshot { let cid = decoded_tags.get_cid(); let mut cluster = - decide_cluster(cid).expect("Couldn't find cluster implementation for class {cid}"); + decide_cluster(cid).unwrap_or_else(|_| panic!("Couldn't find cluster implementation for class {:?}", cid)); cluster.read_alloc(&mut curr_ref_id, stream); self.clusters.insert(cid, cluster); // hashmap takes ownership of box diff --git a/crates/flutterdec-serwalker/src/stream.rs b/crates/flutterdec-serwalker/src/stream.rs index 5b319d5..e0307a1 100644 --- a/crates/flutterdec-serwalker/src/stream.rs +++ b/crates/flutterdec-serwalker/src/stream.rs @@ -10,6 +10,9 @@ impl<'a> Stream<'a> { if self.byte_stream.len() > pos && pos >= 0 { self.curr_stream_offset = pos; } + else { + panic!("Trying to seek an offset at a position past the end of the bytestream. Position {pos}"); + } } pub fn advance_pos(&mut self, num_bytes: usize) { From ccd55ab288c220d889e8ad3fc47252f1f2285a33 Mon Sep 17 00:00:00 2001 From: kalixtez Date: Sun, 31 May 2026 13:01:00 -0500 Subject: [PATCH 28/50] refactor(serwalker): ran cargo fmt --all :DDD --- crates/flutterdec-serwalker/src/snapshot.rs | 5 +++-- crates/flutterdec-serwalker/src/stream.rs | 3 +-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/flutterdec-serwalker/src/snapshot.rs b/crates/flutterdec-serwalker/src/snapshot.rs index 506fcf7..cbb8cfc 100644 --- a/crates/flutterdec-serwalker/src/snapshot.rs +++ b/crates/flutterdec-serwalker/src/snapshot.rs @@ -100,8 +100,9 @@ impl DataSnapshot { let decoded_tags: DecodedTags = decode_tags(tags); let cid = decoded_tags.get_cid(); - let mut cluster = - decide_cluster(cid).unwrap_or_else(|_| panic!("Couldn't find cluster implementation for class {:?}", cid)); + let mut cluster = decide_cluster(cid).unwrap_or_else(|_| { + panic!("Couldn't find cluster implementation for class {:?}", cid) + }); cluster.read_alloc(&mut curr_ref_id, stream); self.clusters.insert(cid, cluster); // hashmap takes ownership of box diff --git a/crates/flutterdec-serwalker/src/stream.rs b/crates/flutterdec-serwalker/src/stream.rs index e0307a1..d95a01e 100644 --- a/crates/flutterdec-serwalker/src/stream.rs +++ b/crates/flutterdec-serwalker/src/stream.rs @@ -9,8 +9,7 @@ impl<'a> Stream<'a> { { if self.byte_stream.len() > pos && pos >= 0 { self.curr_stream_offset = pos; - } - else { + } else { panic!("Trying to seek an offset at a position past the end of the bytestream. Position {pos}"); } } From 15060eb8f98f83713904f742abfbe21f3db3e084 Mon Sep 17 00:00:00 2001 From: Kl <64610615+kalixtez@users.noreply.github.com> Date: Sat, 25 Jul 2026 18:59:09 -0500 Subject: [PATCH 29/50] Update crates/flutterdec-serwalker/src/cluster/mod.rs fix(serwalker): decode OneByteString as Latin-1, TwoByteString lossily OneByteString holds raw bytes 0..=255 interpreted as Latin-1, so from_utf8 panicked on anything above 0x7F. TwoByteString may contain unpaired surrogates, so from_utf16 panicked on those. Co-authored-by: Camilo Vera Vidales --- crates/flutterdec-serwalker/src/cluster/mod.rs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/crates/flutterdec-serwalker/src/cluster/mod.rs b/crates/flutterdec-serwalker/src/cluster/mod.rs index da324a3..74d517b 100644 --- a/crates/flutterdec-serwalker/src/cluster/mod.rs +++ b/crates/flutterdec-serwalker/src/cluster/mod.rs @@ -389,9 +389,13 @@ impl Cluster for _StringCluster { match obj.string_type { StrType::OneByte => { - obj.internal_str = stream.read_string( - usize::try_from(obj.length).expect("Cannot convert Smi to usize."), - ); + // OneByteString payload is Latin-1, not UTF-8. Bytes 0x80..=0xFF + // are valid and map to U+0080..U+00FF; from_utf8 rejects them. + let mut decoded = String::with_capacity(obj.length as usize); + for _ in 0..obj.length { + decoded.push(stream.read_byte() as char); + } + obj.internal_str = decoded; } StrType::TwoByte => { // a TwoByteString has exactly `length` 16-bit code units @@ -406,8 +410,8 @@ impl Cluster for _StringCluster { code_units.push(code_unit); } - obj.internal_str = String::from_utf16(&code_units) - .expect("Failed to decode TwoByteString UTF-16 payload"); + // Dart strings may hold unpaired surrogates; never panic on them. + obj.internal_str = String::from_utf16_lossy(&code_units); } } } From 0e76e28702bf4ff130d7473144edbb37b546462a Mon Sep 17 00:00:00 2001 From: kalixtez Date: Sat, 25 Jul 2026 19:31:48 -0500 Subject: [PATCH 30/50] serwalker(feat): commiting a few changes (implementation of a few clusters) before merging with suggestions of the latest PR --- .../flutterdec-serwalker/src/cluster/mod.rs | 89 ++++++++++++++++++- crates/flutterdec-serwalker/src/lib.rs | 5 +- .../src/object_store/mod.rs | 0 .../src/raw_object/mod.rs | 9 +- crates/flutterdec-serwalker/src/snapshot.rs | 1 + crates/flutterdec-serwalker/src/stream.rs | 16 +++- crates/flutterdec-serwalker/src/utils.rs | 4 +- 7 files changed, 113 insertions(+), 11 deletions(-) create mode 100644 crates/flutterdec-serwalker/src/object_store/mod.rs diff --git a/crates/flutterdec-serwalker/src/cluster/mod.rs b/crates/flutterdec-serwalker/src/cluster/mod.rs index da324a3..8cd08e0 100644 --- a/crates/flutterdec-serwalker/src/cluster/mod.rs +++ b/crates/flutterdec-serwalker/src/cluster/mod.rs @@ -1,3 +1,4 @@ +use std::env::var; use std::usize; use crate::constants::{ClassId, ClassId::*, SIGNED_M, UNSIGNED_M}; @@ -342,7 +343,93 @@ DECLARE_VARIABLE_LENGTH_CLUSTER!(ConstMap); DECLARE_VARIABLE_LENGTH_CLUSTER!(ConstSet); DECLARE_VARIABLE_LENGTH_CLUSTER!(CodeSourceMap); DECLARE_VARIABLE_LENGTH_CLUSTER!(CompressedStackMaps); + +impl Cluster for CompressedStackMapsCluster +{ + fn is_fixed_len(&self) -> bool { + false + } + + fn read_alloc(&mut self, last_ref_id: &mut u64, stream: &mut Stream) -> usize { + self.start_of_alloc = stream.get_current_pos(); + self.first_ref_id = *last_ref_id as u32; + + self.obj_count = stream.read_modified_leb128(UNSIGNED_M); + for _obj_idx in 0..self.obj_count + { + let mut obj = Box::::default(); + obj.length = stream.read_modified_leb128(UNSIGNED_M) as u32; + + self.objs.push(obj); + } + + *last_ref_id += self.obj_count; + self.end_of_alloc = stream.get_current_pos(); + + self.end_of_alloc - self.start_of_alloc + } + + fn read_fill(&mut self, stream: &mut Stream) -> usize { + self.start_of_fill = stream.get_current_pos(); + for obj_idx in 0..self.obj_count + { + let obj = &mut self.objs[obj_idx as usize]; + obj.flags_and_size = stream.read_modified_leb128(UNSIGNED_M) as u32; + + obj.data = stream.read_bytes(obj.length as usize); + } + + self.end_of_fill = stream.get_current_pos(); + + self.end_of_fill - self.start_of_fill + } +} + DECLARE_VARIABLE_LENGTH_CLUSTER!(PcDescriptors); + +impl Cluster for PcDescriptorsCluster +{ + fn is_fixed_len(&self) -> bool { + false + } + + fn read_alloc(&mut self, last_ref_id: &mut u64, stream: &mut Stream) -> usize { + self.start_of_alloc = stream.get_current_pos(); + self.first_ref_id = *last_ref_id as u32; + + self.obj_count = stream.read_modified_leb128(UNSIGNED_M); + + for _obj_idx in 0..self.obj_count + { + let mut obj = Box::::default(); + let length = stream.read_modified_leb128(UNSIGNED_M); + + obj.length = length as u32; + self.objs.push(obj); + } + + *last_ref_id += self.obj_count; + + self.end_of_alloc = stream.get_current_pos(); + self.end_of_alloc - self.start_of_alloc + } + + fn read_fill(&mut self, stream: &mut Stream) -> usize { + self.start_of_fill = stream.get_current_pos(); + + for obj_idx in 0..self.obj_count + { + let length = stream.read_modified_leb128(UNSIGNED_M); // its saved twice + let variable_size_data = stream.read_bytes(length as usize); + + let obj = &mut self.objs[obj_idx as usize]; + obj.data = variable_size_data; + } + + self.end_of_fill - self.start_of_fill + } +} + //DECLARE_VARIABLE_LENGTH_CLUSTER!(OneByteString); These only exist when NO COMPRESSED_POINTERS //DECLARE_VARIABLE_LENGTH_CLUSTER!(TwoByteString); DECLARE_VARIABLE_LENGTH_CLUSTER!(_String); @@ -371,7 +458,7 @@ impl Cluster for _StringCluster { self.objs.push(obj); } - *last_ref_id = *last_ref_id + self.obj_count; + *last_ref_id += self.obj_count; self.end_of_alloc = stream.get_current_pos(); self.end_of_alloc - self.start_of_alloc diff --git a/crates/flutterdec-serwalker/src/lib.rs b/crates/flutterdec-serwalker/src/lib.rs index b5aaf10..29bcf02 100644 --- a/crates/flutterdec-serwalker/src/lib.rs +++ b/crates/flutterdec-serwalker/src/lib.rs @@ -3,6 +3,7 @@ mod utils; mod cluster; mod raw_object; -mod stream; - mod snapshot; +mod object_store; + +mod stream; \ No newline at end of file diff --git a/crates/flutterdec-serwalker/src/object_store/mod.rs b/crates/flutterdec-serwalker/src/object_store/mod.rs new file mode 100644 index 0000000..e69de29 diff --git a/crates/flutterdec-serwalker/src/raw_object/mod.rs b/crates/flutterdec-serwalker/src/raw_object/mod.rs index 1927024..f44c873 100644 --- a/crates/flutterdec-serwalker/src/raw_object/mod.rs +++ b/crates/flutterdec-serwalker/src/raw_object/mod.rs @@ -153,12 +153,15 @@ pub struct CodeSourceMap { #[derive(Default)] pub struct CompressedStackMaps { - // Fieldless class + pub length: u32, + pub flags_and_size: u32, + pub data: Vec } #[derive(Default)] -pub struct PcDescriptors { - // Fieldless class +pub struct PcDescriptors { // not really + pub length: u32, + pub data: Vec } #[derive(Default)] diff --git a/crates/flutterdec-serwalker/src/snapshot.rs b/crates/flutterdec-serwalker/src/snapshot.rs index cbb8cfc..bcc77da 100644 --- a/crates/flutterdec-serwalker/src/snapshot.rs +++ b/crates/flutterdec-serwalker/src/snapshot.rs @@ -130,6 +130,7 @@ pub fn parse_snapshot(stream: &mut Stream) -> DataSnapshot { println!("Now parsing the snapshot..."); snapshot.parse_header(stream); snapshot.parse_clusters(stream); + snapshot.parse_roots(stream); snapshot } diff --git a/crates/flutterdec-serwalker/src/stream.rs b/crates/flutterdec-serwalker/src/stream.rs index d95a01e..fb5818e 100644 --- a/crates/flutterdec-serwalker/src/stream.rs +++ b/crates/flutterdec-serwalker/src/stream.rs @@ -7,7 +7,7 @@ pub struct Stream<'a> { impl<'a> Stream<'a> { fn seek(&mut self, pos: usize) // might be useful? { - if self.byte_stream.len() > pos && pos >= 0 { + if self.byte_stream.len() > pos { self.curr_stream_offset = pos; } else { panic!("Trying to seek an offset at a position past the end of the bytestream. Position {pos}"); @@ -146,7 +146,7 @@ impl<'a> Stream<'a> { if byte < 0 { ref_id += byte as i32; self.advance_pos(1); - return (ref_id + 128) as u32; + return (ref_id + 128) as u32; // same as ref_id & 127 } loop { @@ -165,4 +165,16 @@ impl<'a> Stream<'a> { (ref_id + 128) as u32 // ref_ids are always unsigned } + + pub fn read_bytes(&mut self, len: usize) -> Vec { // copies the data + let bytes = &self.byte_stream[self.curr_stream_offset .. self.curr_stream_offset + len]; + self.advance_pos(len); + bytes.to_vec() + } + + pub fn read_bytes_zero_copy(&mut self, len: usize) -> &'a [u8] { // borrows the data + let bytes = &self.byte_stream[self.curr_stream_offset .. self.curr_stream_offset + len]; + self.advance_pos(len); + bytes + } } diff --git a/crates/flutterdec-serwalker/src/utils.rs b/crates/flutterdec-serwalker/src/utils.rs index b448c7c..92b9c71 100644 --- a/crates/flutterdec-serwalker/src/utils.rs +++ b/crates/flutterdec-serwalker/src/utils.rs @@ -1,5 +1,3 @@ -use paste::paste; - use crate::constants::ClassId; #[macro_export] @@ -37,7 +35,7 @@ macro_rules! DECLARE_FIXED_LENGTH_CLUSTER { self.objs.push(Box::<$name >::default()); } - *last_ref_id = *last_ref_id + self.obj_count; + *last_ref_id += self.obj_count; self.end_of_alloc = stream.get_current_pos(); self.end_of_alloc - self.start_of_alloc From a518dd480ba365b1e89e270de45d76f42c076351 Mon Sep 17 00:00:00 2001 From: kalixtez Date: Sat, 25 Jul 2026 19:58:14 -0500 Subject: [PATCH 31/50] refactor(serwalker): mirror Dart's Read/ReadUnsigned split in Stream Deserializer::Read dispatches on sizeof(T): 1 byte is raw, 2/4/8 are LEB128 with kEndByteMarker (0xC0). ReadUnsigned uses 0x80. Exposing the raw marker as a parameter made picking wrong the default outcome. Reads now return Result and bounds check. --- Cargo.lock | 1 + crates/flutterdec-serwalker/Cargo.toml | 1 + .../flutterdec-serwalker/src/cluster/mod.rs | 326 +++++++++--------- crates/flutterdec-serwalker/src/snapshot.rs | 46 +-- crates/flutterdec-serwalker/src/stream.rs | 220 +++++------- crates/flutterdec-serwalker/src/utils.rs | 10 +- 6 files changed, 274 insertions(+), 330 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6482e4e..a49a408 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -365,6 +365,7 @@ dependencies = [ name = "flutterdec-serwalker" version = "0.1.0-alpha.2" dependencies = [ + "anyhow", "goblin", "paste", ] diff --git a/crates/flutterdec-serwalker/Cargo.toml b/crates/flutterdec-serwalker/Cargo.toml index 1885862..b82e49c 100644 --- a/crates/flutterdec-serwalker/Cargo.toml +++ b/crates/flutterdec-serwalker/Cargo.toml @@ -5,5 +5,6 @@ edition.workspace = true license.workspace = true [dependencies] +anyhow.workspace = true goblin.workspace = true paste = "1.0.15" diff --git a/crates/flutterdec-serwalker/src/cluster/mod.rs b/crates/flutterdec-serwalker/src/cluster/mod.rs index 636c2c0..652e262 100644 --- a/crates/flutterdec-serwalker/src/cluster/mod.rs +++ b/crates/flutterdec-serwalker/src/cluster/mod.rs @@ -10,14 +10,14 @@ use crate::FFI_TYPES_LIST; pub trait Cluster { fn is_fixed_len(&self) -> bool; - fn read_alloc(&mut self, last_ref_id: &mut u64, stream: &mut Stream) -> usize; - fn read_fill(&mut self, stream: &mut Stream) -> usize; + fn read_alloc(&mut self, last_ref_id: &mut u64, stream: &mut Stream) -> anyhow::Result; + fn read_fill(&mut self, stream: &mut Stream) -> anyhow::Result; } -pub fn read_smi(stream: &mut Stream) -> Smi { - let raw_smi = stream.read_modified_leb128(SIGNED_M); // smis are always written as signed numbers +pub fn read_smi(stream: &mut Stream) -> anyhow::Result { + let raw_smi = stream.read()?; // smis are always written as signed numbers - raw_smi as Smi + Ok(raw_smi as Smi) } macro_rules! FFI_CASE_PATTERN { @@ -42,72 +42,72 @@ pub fn decide_cluster(class_id: ClassId) -> Result, &'static st DECLARE_FIXED_LENGTH_CLUSTER!(TypeParameters, |_self, stream| { for obj_idx in 0.._self.obj_count as usize { let obj = &mut *_self.objs[obj_idx]; - obj.names = stream.read_ref_id(); - obj.flags = stream.read_ref_id(); - obj.bounds = stream.read_ref_id(); - obj.defaults = stream.read_ref_id(); + obj.names = stream.read_ref_id()?; + obj.flags = stream.read_ref_id()?; + obj.bounds = stream.read_ref_id()?; + obj.defaults = stream.read_ref_id()?; } }); DECLARE_FIXED_LENGTH_CLUSTER!(PatchClass, |_self, stream| { for obj_idx in 0.._self.obj_count as usize { let obj = &mut *_self.objs[obj_idx]; - obj.wrapped_class = stream.read_ref_id(); - obj.script = stream.read_ref_id(); - obj.kernel_program_info = stream.read_ref_id(); + obj.wrapped_class = stream.read_ref_id()?; + obj.script = stream.read_ref_id()?; + obj.kernel_program_info = stream.read_ref_id()?; } }); DECLARE_FIXED_LENGTH_CLUSTER!(Function, |_self, stream| { for obj_idx in 0.._self.obj_count as usize { let obj = &mut *_self.objs[obj_idx]; - obj.name = stream.read_ref_id(); - obj.owner = stream.read_ref_id(); - obj.signature = stream.read_ref_id(); - obj.data = stream.read_ref_id(); - obj.ic_data_array_or_bytecode = stream.read_ref_id(); - obj.code = stream.read_ref_id(); - obj.positional_parameter_names = stream.read_ref_id(); - obj.unoptimized_code = stream.read_ref_id(); - obj.bitmap = stream.read_modified_leb128(UNSIGNED_M) as u64; - obj.kernel_offset = stream.read_modified_leb128(UNSIGNED_M) as u32; - obj.kind_tag = stream.read_modified_leb128(UNSIGNED_M) as u32; + obj.name = stream.read_ref_id()?; + obj.owner = stream.read_ref_id()?; + obj.signature = stream.read_ref_id()?; + obj.data = stream.read_ref_id()?; + obj.ic_data_array_or_bytecode = stream.read_ref_id()?; + obj.code = stream.read_ref_id()?; + obj.positional_parameter_names = stream.read_ref_id()?; + obj.unoptimized_code = stream.read_ref_id()?; + obj.bitmap = stream.read_unsigned()? as u64; + obj.kernel_offset = stream.read_unsigned()? as u32; + obj.kind_tag = stream.read_unsigned()? as u32; } }); DECLARE_FIXED_LENGTH_CLUSTER!(ClosureData, |_self, stream| { for obj_idx in 0.._self.obj_count as usize { let obj = &mut *_self.objs[obj_idx]; - obj.context_scope = stream.read_ref_id(); - obj.parent_function = stream.read_ref_id(); - obj.closure = stream.read_ref_id(); - obj.packed_fields = stream.read_modified_leb128(UNSIGNED_M) as u32; + obj.context_scope = stream.read_ref_id()?; + obj.parent_function = stream.read_ref_id()?; + obj.closure = stream.read_ref_id()?; + obj.packed_fields = stream.read_unsigned()? as u32; } }); DECLARE_FIXED_LENGTH_CLUSTER!(FfiTrampolineData, |_self, stream| { for obj_idx in 0.._self.obj_count as usize { let obj = &mut *_self.objs[obj_idx]; - obj.signature_type = stream.read_ref_id(); - obj.c_signature = stream.read_ref_id(); - obj.callback_target = stream.read_ref_id(); - obj.callback_exceptional_return = stream.read_ref_id(); - obj.ffi_function_kind = stream.read_modified_leb128(UNSIGNED_M) as u8; - obj.callback_id = stream.read_modified_leb128(SIGNED_M) as i32; + obj.signature_type = stream.read_ref_id()?; + obj.c_signature = stream.read_ref_id()?; + obj.callback_target = stream.read_ref_id()?; + obj.callback_exceptional_return = stream.read_ref_id()?; + obj.ffi_function_kind = stream.read_unsigned()? as u8; + obj.callback_id = stream.read()? as i32; } }); DECLARE_FIXED_LENGTH_CLUSTER!(Field, |_self, stream| { for obj_idx in 0.._self.obj_count as usize { let obj = &mut *_self.objs[obj_idx]; - obj.name = stream.read_ref_id(); - obj.owner = stream.read_ref_id(); - obj.type_field = stream.read_ref_id(); - obj.initializer_function = stream.read_ref_id(); - obj.host_offset_or_field_id = stream.read_ref_id(); - obj.guarded_list_length = stream.read_ref_id(); - obj.exact_type = stream.read_ref_id(); - obj.dependent_code = stream.read_ref_id(); - obj.kernel_offset = stream.read_modified_leb128(UNSIGNED_M) as u32; - obj.guarded_list_length_in_object_offset = stream.read_modified_leb128(SIGNED_M) as i8; - obj.static_type_exactness_state = stream.read_modified_leb128(SIGNED_M) as i8; - obj.target_offset = stream.read_modified_leb128(SIGNED_M) as i32; - obj.kind_bits = stream.read_modified_leb128(UNSIGNED_M) as u32; + obj.name = stream.read_ref_id()?; + obj.owner = stream.read_ref_id()?; + obj.type_field = stream.read_ref_id()?; + obj.initializer_function = stream.read_ref_id()?; + obj.host_offset_or_field_id = stream.read_ref_id()?; + obj.guarded_list_length = stream.read_ref_id()?; + obj.exact_type = stream.read_ref_id()?; + obj.dependent_code = stream.read_ref_id()?; + obj.kernel_offset = stream.read_unsigned()? as u32; + obj.guarded_list_length_in_object_offset = stream.read()? as i8; + obj.static_type_exactness_state = stream.read()? as i8; + obj.target_offset = stream.read()? as i32; + obj.kind_bits = stream.read_unsigned()? as u32; } }); DECLARE_FIXED_LENGTH_CLUSTER!(Script, |_self, stream| { @@ -116,158 +116,158 @@ DECLARE_FIXED_LENGTH_CLUSTER!(Script, |_self, stream| { DECLARE_FIXED_LENGTH_CLUSTER!(Library, |_self, stream| { for obj_idx in 0.._self.obj_count as usize { let obj = &mut *_self.objs[obj_idx]; - obj.name = stream.read_ref_id(); - obj.url = stream.read_ref_id(); - obj.private_key = stream.read_ref_id(); - obj.dictionary = stream.read_ref_id(); - obj.metadata = stream.read_ref_id(); - obj.toplevel_class = stream.read_ref_id(); - obj.used_scripts = stream.read_ref_id(); - obj.loading_unit = stream.read_ref_id(); - obj.imports = stream.read_ref_id(); - obj.exports = stream.read_ref_id(); - obj.dependencies = stream.read_ref_id(); - obj.kernel_program_info = stream.read_ref_id(); - obj.loaded_scripts = stream.read_ref_id(); - obj.num_imports = stream.read_modified_leb128(UNSIGNED_M) as u16; - obj.flags = stream.read_modified_leb128(UNSIGNED_M) as u8; - obj.kernel_library_index = stream.read_modified_leb128(UNSIGNED_M) as u32; - obj.load_state = stream.read_modified_leb128(SIGNED_M) as i8; + obj.name = stream.read_ref_id()?; + obj.url = stream.read_ref_id()?; + obj.private_key = stream.read_ref_id()?; + obj.dictionary = stream.read_ref_id()?; + obj.metadata = stream.read_ref_id()?; + obj.toplevel_class = stream.read_ref_id()?; + obj.used_scripts = stream.read_ref_id()?; + obj.loading_unit = stream.read_ref_id()?; + obj.imports = stream.read_ref_id()?; + obj.exports = stream.read_ref_id()?; + obj.dependencies = stream.read_ref_id()?; + obj.kernel_program_info = stream.read_ref_id()?; + obj.loaded_scripts = stream.read_ref_id()?; + obj.num_imports = stream.read_unsigned()? as u16; + obj.flags = stream.read_unsigned()? as u8; + obj.kernel_library_index = stream.read_unsigned()? as u32; + obj.load_state = stream.read()? as i8; } }); DECLARE_FIXED_LENGTH_CLUSTER!(Namespace, |_self, stream| { for obj_idx in 0.._self.obj_count as usize { let obj = &mut *_self.objs[obj_idx]; - obj.target = stream.read_ref_id(); - obj.show_names = stream.read_ref_id(); - obj.hide_names = stream.read_ref_id(); - obj.owner = stream.read_ref_id(); + obj.target = stream.read_ref_id()?; + obj.show_names = stream.read_ref_id()?; + obj.hide_names = stream.read_ref_id()?; + obj.owner = stream.read_ref_id()?; } }); DECLARE_FIXED_LENGTH_CLUSTER!(KernelProgramInfo, |_self, stream| { for obj_idx in 0.._self.obj_count as usize { let obj = &mut *_self.objs[obj_idx]; - obj.kernel_component = stream.read_ref_id(); - obj.string_offsets = stream.read_ref_id(); - obj.string_data = stream.read_ref_id(); - obj.canonical_names = stream.read_ref_id(); - obj.metadata_payloads = stream.read_ref_id(); - obj.metadata_mappings = stream.read_ref_id(); - obj.scripts = stream.read_ref_id(); - obj.constants = stream.read_ref_id(); - obj.constants_table = stream.read_ref_id(); - obj.libraries_cache = stream.read_ref_id(); - obj.classes_cache = stream.read_ref_id(); + obj.kernel_component = stream.read_ref_id()?; + obj.string_offsets = stream.read_ref_id()?; + obj.string_data = stream.read_ref_id()?; + obj.canonical_names = stream.read_ref_id()?; + obj.metadata_payloads = stream.read_ref_id()?; + obj.metadata_mappings = stream.read_ref_id()?; + obj.scripts = stream.read_ref_id()?; + obj.constants = stream.read_ref_id()?; + obj.constants_table = stream.read_ref_id()?; + obj.libraries_cache = stream.read_ref_id()?; + obj.classes_cache = stream.read_ref_id()?; } }); DECLARE_FIXED_LENGTH_CLUSTER!(UnlinkedCall, |_self, stream| { for obj_idx in 0.._self.obj_count as usize { let obj = &mut *_self.objs[obj_idx]; - obj.can_patch_to_monomorphic = stream.read_modified_leb128(UNSIGNED_M) != 0; + obj.can_patch_to_monomorphic = stream.read_unsigned()? != 0; } }); DECLARE_FIXED_LENGTH_CLUSTER!(ICData, |_self, stream| { for obj_idx in 0.._self.obj_count as usize { let obj = &mut *_self.objs[obj_idx]; - obj.state_bits = stream.read_modified_leb128(UNSIGNED_M) as u32; + obj.state_bits = stream.read_unsigned()? as u32; } }); DECLARE_FIXED_LENGTH_CLUSTER!(MegamorphicCache, |_self, stream| { for obj_idx in 0.._self.obj_count as usize { let obj = &mut *_self.objs[obj_idx]; - obj.filled_entry_count = stream.read_modified_leb128(SIGNED_M) as i32; + obj.filled_entry_count = stream.read()? as i32; } }); DECLARE_FIXED_LENGTH_CLUSTER!(SubtypeTestCache, |_self, stream| { for obj_idx in 0.._self.obj_count as usize { let obj = &mut *_self.objs[obj_idx]; - obj.num_inputs = stream.read_modified_leb128(UNSIGNED_M) as u32; - obj.num_occupied = stream.read_modified_leb128(UNSIGNED_M) as u32; + obj.num_inputs = stream.read_unsigned()? as u32; + obj.num_occupied = stream.read_unsigned()? as u32; } }); DECLARE_FIXED_LENGTH_CLUSTER!(LoadingUnit, |_self, stream| { for obj_idx in 0.._self.obj_count as usize { let obj = &mut *_self.objs[obj_idx]; - obj.parent = stream.read_ref_id(); - obj.base_objects = stream.read_ref_id(); - obj.packed_fields = stream.read_modified_leb128(SIGNED_M) as i64; + obj.parent = stream.read_ref_id()?; + obj.base_objects = stream.read_ref_id()?; + obj.packed_fields = stream.read()? as i64; } }); DECLARE_FIXED_LENGTH_CLUSTER!(LanguageError, |_self, stream| { for obj_idx in 0.._self.obj_count as usize { let obj = &mut *_self.objs[obj_idx]; - obj.previous_error = stream.read_ref_id(); - obj.script = stream.read_ref_id(); - obj.message = stream.read_ref_id(); - obj.formatted_message = stream.read_ref_id(); - obj.kind = stream.read_modified_leb128(SIGNED_M) as i8; - obj.report_after_token = stream.read_modified_leb128(UNSIGNED_M) != 0; + obj.previous_error = stream.read_ref_id()?; + obj.script = stream.read_ref_id()?; + obj.message = stream.read_ref_id()?; + obj.formatted_message = stream.read_ref_id()?; + obj.kind = stream.read()? as i8; + obj.report_after_token = stream.read_unsigned()? != 0; } }); DECLARE_FIXED_LENGTH_CLUSTER!(UnhandledException, |_self, stream| { for obj_idx in 0.._self.obj_count as usize { let obj = &mut *_self.objs[obj_idx]; - obj.exception = stream.read_ref_id(); - obj.stacktrace = stream.read_ref_id(); + obj.exception = stream.read_ref_id()?; + obj.stacktrace = stream.read_ref_id()?; } }); DECLARE_FIXED_LENGTH_CLUSTER!(LibraryPrefix, |_self, stream| { for obj_idx in 0.._self.obj_count as usize { let obj = &mut *_self.objs[obj_idx]; - obj.name = stream.read_ref_id(); - obj.imports = stream.read_ref_id(); - obj.importer = stream.read_ref_id(); - obj.num_imports = stream.read_modified_leb128(UNSIGNED_M) as u16; - obj.is_deferred_load = stream.read_modified_leb128(UNSIGNED_M) != 0; + obj.name = stream.read_ref_id()?; + obj.imports = stream.read_ref_id()?; + obj.importer = stream.read_ref_id()?; + obj.num_imports = stream.read_unsigned()? as u16; + obj.is_deferred_load = stream.read_unsigned()? != 0; } }); DECLARE_FIXED_LENGTH_CLUSTER!(Type, |_self, stream| { for obj_idx in 0.._self.obj_count as usize { let obj = &mut *_self.objs[obj_idx]; - obj.arguments = stream.read_ref_id(); + obj.arguments = stream.read_ref_id()?; } }); DECLARE_FIXED_LENGTH_CLUSTER!(FunctionType, |_self, stream| { for obj_idx in 0.._self.obj_count as usize { let obj = &mut *_self.objs[obj_idx]; - obj.type_parameters = stream.read_ref_id(); - obj.result_type = stream.read_ref_id(); - obj.parameter_types = stream.read_ref_id(); - obj.named_parameter_names = stream.read_ref_id(); - obj.packed_parameter_counts = stream.read_modified_leb128(UNSIGNED_M) as u32; - obj.packed_type_parameter_counts = stream.read_modified_leb128(UNSIGNED_M) as u16; + obj.type_parameters = stream.read_ref_id()?; + obj.result_type = stream.read_ref_id()?; + obj.parameter_types = stream.read_ref_id()?; + obj.named_parameter_names = stream.read_ref_id()?; + obj.packed_parameter_counts = stream.read_unsigned()? as u32; + obj.packed_type_parameter_counts = stream.read_unsigned()? as u16; } }); DECLARE_FIXED_LENGTH_CLUSTER!(RecordType, |_self, stream| { for obj_idx in 0.._self.obj_count as usize { let obj = &mut *_self.objs[obj_idx]; - obj.field_types = stream.read_ref_id(); - obj.shape = stream.read_ref_id() as i32; + obj.field_types = stream.read_ref_id()?; + obj.shape = stream.read_ref_id()? as i32; } }); DECLARE_FIXED_LENGTH_CLUSTER!(TypeParameter, |_self, stream| { for obj_idx in 0.._self.obj_count as usize { let obj = &mut *_self.objs[obj_idx]; - obj.owner = stream.read_ref_id(); - obj.base = stream.read_modified_leb128(UNSIGNED_M) as u16; - obj.index = stream.read_modified_leb128(UNSIGNED_M) as u16; + obj.owner = stream.read_ref_id()?; + obj.base = stream.read_unsigned()? as u16; + obj.index = stream.read_unsigned()? as u16; } }); DECLARE_FIXED_LENGTH_CLUSTER!(Closure, |_self, stream| { for obj_idx in 0.._self.obj_count as usize { let obj = &mut *_self.objs[obj_idx]; - obj.instantiator_type_arguments = stream.read_ref_id(); - obj.function_type_arguments = stream.read_ref_id(); - obj.delayed_type_arguments = stream.read_ref_id(); - obj.function = stream.read_ref_id(); - obj.context = stream.read_ref_id(); - obj.hash = stream.read_ref_id(); + obj.instantiator_type_arguments = stream.read_ref_id()?; + obj.function_type_arguments = stream.read_ref_id()?; + obj.delayed_type_arguments = stream.read_ref_id()?; + obj.function = stream.read_ref_id()?; + obj.context = stream.read_ref_id()?; + obj.hash = stream.read_ref_id()?; } }); DECLARE_FIXED_LENGTH_CLUSTER!(Double, |_self, stream| { for obj_idx in 0.._self.obj_count as usize { let obj = &mut *_self.objs[obj_idx]; - obj.value = f64::from_bits(stream.read_u64()); + obj.value = f64::from_bits(stream.read_raw_u64()?); } }); DECLARE_FIXED_LENGTH_CLUSTER!(Int32x4, |_self, stream| { @@ -276,16 +276,16 @@ DECLARE_FIXED_LENGTH_CLUSTER!(Int32x4, |_self, stream| { DECLARE_FIXED_LENGTH_CLUSTER!(GrowableObjectArray, |_self, stream| { for obj_idx in 0.._self.obj_count as usize { let obj = &mut *_self.objs[obj_idx]; - obj.type_arguments = stream.read_ref_id(); - obj.data = stream.read_ref_id(); - obj.length = stream.read_ref_id() as i32; + obj.type_arguments = stream.read_ref_id()?; + obj.data = stream.read_ref_id()?; + obj.length = stream.read_ref_id()? as i32; } }); DECLARE_FIXED_LENGTH_CLUSTER!(TypedDataView, |_self, stream| { for obj_idx in 0.._self.obj_count as usize { let obj = &mut *_self.objs[obj_idx]; - obj.typed_data = stream.read_ref_id(); - obj.offset_in_bytes = stream.read_ref_id() as i32; + obj.typed_data = stream.read_ref_id()?; + obj.offset_in_bytes = stream.read_ref_id()? as i32; } }); DECLARE_FIXED_LENGTH_CLUSTER!(ExternalTypedData, |_self, stream| { @@ -294,30 +294,30 @@ DECLARE_FIXED_LENGTH_CLUSTER!(ExternalTypedData, |_self, stream| { DECLARE_FIXED_LENGTH_CLUSTER!(StackTrace, |_self, stream| { for obj_idx in 0.._self.obj_count as usize { let obj = &mut *_self.objs[obj_idx]; - obj.async_link = stream.read_ref_id(); - obj.code_array = stream.read_ref_id(); - obj.pc_offset_array = stream.read_ref_id(); - obj.expand_inlined = stream.read_modified_leb128(UNSIGNED_M) != 0; + obj.async_link = stream.read_ref_id()?; + obj.code_array = stream.read_ref_id()?; + obj.pc_offset_array = stream.read_ref_id()?; + obj.expand_inlined = stream.read_unsigned()? != 0; } }); DECLARE_FIXED_LENGTH_CLUSTER!(RegExp, |_self, stream| { for obj_idx in 0.._self.obj_count as usize { let obj = &mut *_self.objs[obj_idx]; - obj.capture_name_map = stream.read_ref_id(); - obj.pattern = stream.read_ref_id(); - obj.one_byte = stream.read_ref_id(); - obj.two_byte = stream.read_ref_id(); - obj.one_byte_sticky = stream.read_ref_id(); - obj.two_byte_sticky = stream.read_ref_id(); - obj.flags = stream.read_modified_leb128(UNSIGNED_M) as u32; + obj.capture_name_map = stream.read_ref_id()?; + obj.pattern = stream.read_ref_id()?; + obj.one_byte = stream.read_ref_id()?; + obj.two_byte = stream.read_ref_id()?; + obj.one_byte_sticky = stream.read_ref_id()?; + obj.two_byte_sticky = stream.read_ref_id()?; + obj.flags = stream.read_unsigned()? as u32; } }); DECLARE_FIXED_LENGTH_CLUSTER!(WeakProperty, |_self, stream| { for obj_idx in 0.._self.obj_count as usize { let obj = &mut *_self.objs[obj_idx]; - obj.key = stream.read_ref_id(); - obj.value = stream.read_ref_id(); - obj.next_seen_by_gc = stream.read_ref_id(); + obj.key = stream.read_ref_id()?; + obj.value = stream.read_ref_id()?; + obj.next_seen_by_gc = stream.read_ref_id()?; } }); @@ -350,15 +350,15 @@ impl Cluster for CompressedStackMapsCluster false } - fn read_alloc(&mut self, last_ref_id: &mut u64, stream: &mut Stream) -> usize { + fn read_alloc(&mut self, last_ref_id: &mut u64, stream: &mut Stream) -> anyhow::Result { self.start_of_alloc = stream.get_current_pos(); self.first_ref_id = *last_ref_id as u32; - self.obj_count = stream.read_modified_leb128(UNSIGNED_M); + self.obj_count = stream.read_unsigned()?; for _obj_idx in 0..self.obj_count { let mut obj = Box::::default(); - obj.length = stream.read_modified_leb128(UNSIGNED_M) as u32; + obj.length = stream.read_unsigned()? as u32; self.objs.push(obj); } @@ -366,22 +366,22 @@ impl Cluster for CompressedStackMapsCluster *last_ref_id += self.obj_count; self.end_of_alloc = stream.get_current_pos(); - self.end_of_alloc - self.start_of_alloc + Ok(self.end_of_alloc - self.start_of_alloc) } - fn read_fill(&mut self, stream: &mut Stream) -> usize { + fn read_fill(&mut self, stream: &mut Stream) -> anyhow::Result { self.start_of_fill = stream.get_current_pos(); for obj_idx in 0..self.obj_count { let obj = &mut self.objs[obj_idx as usize]; - obj.flags_and_size = stream.read_modified_leb128(UNSIGNED_M) as u32; + obj.flags_and_size = stream.read_unsigned()? as u32; - obj.data = stream.read_bytes(obj.length as usize); + obj.data = stream.read_bytes(obj.length as usize)?; } self.end_of_fill = stream.get_current_pos(); - self.end_of_fill - self.start_of_fill + Ok(self.end_of_fill - self.start_of_fill) } } @@ -393,16 +393,16 @@ impl Cluster for PcDescriptorsCluster false } - fn read_alloc(&mut self, last_ref_id: &mut u64, stream: &mut Stream) -> usize { + fn read_alloc(&mut self, last_ref_id: &mut u64, stream: &mut Stream) -> anyhow::Result { self.start_of_alloc = stream.get_current_pos(); self.first_ref_id = *last_ref_id as u32; - self.obj_count = stream.read_modified_leb128(UNSIGNED_M); + self.obj_count = stream.read_unsigned()?; for _obj_idx in 0..self.obj_count { let mut obj = Box::::default(); - let length = stream.read_modified_leb128(UNSIGNED_M); + let length = stream.read_unsigned()?; obj.length = length as u32; self.objs.push(obj); @@ -411,22 +411,22 @@ impl Cluster for PcDescriptorsCluster *last_ref_id += self.obj_count; self.end_of_alloc = stream.get_current_pos(); - self.end_of_alloc - self.start_of_alloc + Ok(self.end_of_alloc - self.start_of_alloc) } - fn read_fill(&mut self, stream: &mut Stream) -> usize { + fn read_fill(&mut self, stream: &mut Stream) -> anyhow::Result { self.start_of_fill = stream.get_current_pos(); for obj_idx in 0..self.obj_count { - let length = stream.read_modified_leb128(UNSIGNED_M); // its saved twice - let variable_size_data = stream.read_bytes(length as usize); + let length = stream.read_unsigned()?; // its saved twice + let variable_size_data = stream.read_bytes(length as usize)?; let obj = &mut self.objs[obj_idx as usize]; obj.data = variable_size_data; } - self.end_of_fill - self.start_of_fill + Ok(self.end_of_fill - self.start_of_fill) } } @@ -439,15 +439,15 @@ impl Cluster for _StringCluster { false } - fn read_alloc(&mut self, last_ref_id: &mut u64, stream: &mut Stream) -> usize { + fn read_alloc(&mut self, last_ref_id: &mut u64, stream: &mut Stream) -> anyhow::Result { self.start_of_alloc = stream.get_current_pos(); self.first_ref_id = *last_ref_id as u32; - self.obj_count = stream.read_modified_leb128(UNSIGNED_M); + self.obj_count = stream.read_unsigned()?; for _obj_idx in 0..self.obj_count { let mut obj = Box::<_String>::default(); - let encoded = stream.read_modified_leb128(UNSIGNED_M); + let encoded = stream.read_unsigned()?; obj.string_type = if encoded & 1 == 1 { StrType::TwoByte } else { @@ -461,14 +461,14 @@ impl Cluster for _StringCluster { *last_ref_id += self.obj_count; self.end_of_alloc = stream.get_current_pos(); - self.end_of_alloc - self.start_of_alloc + Ok(self.end_of_alloc - self.start_of_alloc) } - fn read_fill(&mut self, stream: &mut Stream) -> usize { + fn read_fill(&mut self, stream: &mut Stream) -> anyhow::Result { self.start_of_fill = stream.get_current_pos(); for obj_idx in 0..self.obj_count { - let _encoded = stream.read_modified_leb128(UNSIGNED_M); // why is this here twice? Huh? + let _encoded = stream.read_unsigned()?; // why is this here twice? Huh? let obj = self.objs.get_mut(obj_idx as usize); let obj = @@ -480,7 +480,7 @@ impl Cluster for _StringCluster { // are valid and map to U+0080..U+00FF; from_utf8 rejects them. let mut decoded = String::with_capacity(obj.length as usize); for _ in 0..obj.length { - decoded.push(stream.read_byte() as char); + decoded.push(stream.read_byte()? as char); } obj.internal_str = decoded; } @@ -490,8 +490,8 @@ impl Cluster for _StringCluster { for _ in 0..obj.length { // read 2 bytes (little-endian) - let b1 = stream.read_byte() as u16; - let b2 = stream.read_byte() as u16; + let b1 = stream.read_byte()? as u16; + let b2 = stream.read_byte()? as u16; let code_unit = b1 | (b2 << 8); code_units.push(code_unit); @@ -504,6 +504,6 @@ impl Cluster for _StringCluster { } self.end_of_fill = stream.get_current_pos(); - self.end_of_fill - self.start_of_fill + Ok(self.end_of_fill - self.start_of_fill) } } diff --git a/crates/flutterdec-serwalker/src/snapshot.rs b/crates/flutterdec-serwalker/src/snapshot.rs index bcc77da..abd813b 100644 --- a/crates/flutterdec-serwalker/src/snapshot.rs +++ b/crates/flutterdec-serwalker/src/snapshot.rs @@ -38,9 +38,8 @@ impl TryFrom for SnapshotKind { #[derive(Default)] pub struct DataSnapshot { - // this array will contain mutable references to all clusters, and it will be indexed using the class id - clusters: HashMap>, // thus each cluster must have its own UNIQUE class id - cluster_order: Vec, // used in the fill step to know which cluster's read_fill function to call + clusters: HashMap>, + cluster_order: Vec, // used in the fill step to know which cluster's read_fill function to call magic_bytes: u32, size: u64, @@ -65,30 +64,30 @@ pub struct DataSnapshot { impl DataSnapshot { fn parse_version_and_features(&mut self, stream: &mut Stream) { - let mut version_and_features = stream.read_c_string(); + let mut version_and_features = stream.read_c_string().unwrap(); self.features = version_and_features.split_off(constants::VERSION_HASH_LENGTH); // returns (str[hash_len..]) self.version_hash = version_and_features; } fn parse_header(&mut self, stream: &mut Stream) { - self.magic_bytes = stream.read_u32(); + self.magic_bytes = stream.read_raw_u32().unwrap(); if self.magic_bytes != MAGIC_BYTES { panic!("Not a snapshot...") } - self.size = stream.read_u64(); - self.kind = SnapshotKind::try_from(stream.read_u64()).unwrap(); + self.size = stream.read_raw_u64().unwrap(); + self.kind = SnapshotKind::try_from(stream.read_raw_u64().unwrap()).unwrap(); self.parse_version_and_features(stream); - self.num_base_objects = stream.read_modified_leb128(UNSIGNED_M); - self.num_objects = stream.read_modified_leb128(UNSIGNED_M); - self.num_clusters = stream.read_modified_leb128(UNSIGNED_M); + self.num_base_objects = stream.read_unsigned().unwrap(); + self.num_objects = stream.read_unsigned().unwrap(); + self.num_clusters = stream.read_unsigned().unwrap(); - self.instr_table_len = stream.read_modified_leb128(UNSIGNED_M) as usize; - self.instr_table_offset = stream.read_modified_leb128(UNSIGNED_M) as usize; + self.instr_table_len = stream.read_unsigned().unwrap() as usize; + self.instr_table_offset = stream.read_unsigned().unwrap() as usize; } fn parse_clusters(&mut self, stream: &mut Stream) { @@ -96,7 +95,7 @@ impl DataSnapshot { self.start_of_alloc_area = stream.get_current_pos(); for _cluster_idx in 0..self.num_clusters { - let tags: u32 = stream.read_modified_leb128(UNSIGNED_M) as u32; + let tags: u32 = stream.read_unsigned().unwrap() as u32; let decoded_tags: DecodedTags = decode_tags(tags); let cid = decoded_tags.get_cid(); @@ -104,24 +103,24 @@ impl DataSnapshot { panic!("Couldn't find cluster implementation for class {:?}", cid) }); - cluster.read_alloc(&mut curr_ref_id, stream); - self.clusters.insert(cid, cluster); // hashmap takes ownership of box - self.cluster_order.push(cid); + cluster.read_alloc(&mut curr_ref_id, stream).expect("Failed to read alloc for cluster"); + + // Composite key exactly as suggested by PR reviewer + let key = (cid as u32) << 2 | ((decoded_tags.is_canonical() as u32) << 1) | (decoded_tags.is_deeply_immutable() as u32); + self.clusters.insert(key, cluster); + self.cluster_order.push(key); } self.end_of_alloc_area = stream.get_current_pos(); self.start_of_fill_area = stream.get_current_pos(); - for cluster_idx in 0..self.num_clusters { - let cid = self.cluster_order[cluster_idx as usize]; - let cluster_opt = self.clusters.get_mut(&cid); - - let cluster = cluster_opt.unwrap(); // this should never panic - (*cluster).read_fill(stream); + for key in self.cluster_order.iter() { + let cluster = self.clusters.get_mut(key).unwrap(); + (*cluster).read_fill(stream).expect("Failed to read fill for cluster"); } self.end_of_fill_area = stream.get_current_pos(); } - fn parse_roots(&mut self, stream: &mut Stream) {} + fn parse_roots(&mut self, _stream: &mut Stream) {} } pub fn parse_snapshot(stream: &mut Stream) -> DataSnapshot { @@ -134,3 +133,4 @@ pub fn parse_snapshot(stream: &mut Stream) -> DataSnapshot { snapshot } + diff --git a/crates/flutterdec-serwalker/src/stream.rs b/crates/flutterdec-serwalker/src/stream.rs index fb5818e..a35a72a 100644 --- a/crates/flutterdec-serwalker/src/stream.rs +++ b/crates/flutterdec-serwalker/src/stream.rs @@ -1,180 +1,122 @@ -use crate::constants::{DATA_BITS_PER_BYTE, UNSIGNED_END_OF_DATA_BYTE, UNSIGNED_MAX_DATA_PER_BYTE}; +use anyhow::{anyhow, bail, Result}; + +use crate::constants::{DATA_BITS_PER_BYTE, SIGNED_M, UNSIGNED_M, UNSIGNED_MAX_DATA_PER_BYTE}; + pub struct Stream<'a> { byte_stream: &'a [u8], curr_stream_offset: usize, } impl<'a> Stream<'a> { - fn seek(&mut self, pos: usize) // might be useful? - { - if self.byte_stream.len() > pos { - self.curr_stream_offset = pos; - } else { - panic!("Trying to seek an offset at a position past the end of the bytestream. Position {pos}"); + pub fn new(byte_stream: &'a [u8]) -> Self { + Self { byte_stream, curr_stream_offset: 0 } + } + + pub fn seek(&mut self, pos: usize) -> Result<()> { + if pos > self.byte_stream.len() { + bail!("seek to {pos} past end of snapshot ({})", self.byte_stream.len()); } + self.curr_stream_offset = pos; + Ok(()) } - pub fn advance_pos(&mut self, num_bytes: usize) { - self.curr_stream_offset += num_bytes; + fn take(&mut self, n: usize) -> Result<&'a [u8]> { + let end = self + .curr_stream_offset + .checked_add(n) + .filter(|e| *e <= self.byte_stream.len()) + .ok_or_else(|| { + anyhow!("read of {n} bytes past end of snapshot at offset {}", self.curr_stream_offset) + })?; + let slice = &self.byte_stream[self.curr_stream_offset..end]; + self.curr_stream_offset = end; + Ok(slice) } pub fn get_current_pos(&self) -> usize { self.curr_stream_offset } - /* - Reads a modified uleb from the current stream offset. - - Dart uses a modified LEB128 format. The normal format uses bytes with their MSb set in order - to signify that there are more bytes ahead, and the last byte has its MSb unset, whereas Dart's - implementation does the opposite. The "continuation" bit on each byte is 0, and the last byte - has its MSb set. - */ - pub fn read_modified_leb128(&mut self, sign_marker: u8) -> u64 // 8 bytes should be enough for anything... - { - let mut idx: u8 = 0; - - let first_byte = self.byte_stream[self.curr_stream_offset]; - if first_byte > UNSIGNED_MAX_DATA_PER_BYTE - // if the first byte has its MSb set - { - self.advance_pos(1); - // wrapping_sub mimics C++ unsigned underflow, giving us perfect sign-extension - // for negative numbers, while behaving normally for positive numbers. gotta get used to this :) - return (first_byte as u64).wrapping_sub(sign_marker as u64); - } - - let mut read_num: u64 = 0; - let mut byte: u8; + /// Dart's modified LEB128: little endian 7 bit groups, continuation bit 0, + /// final byte has its MSb set. ReadStream::Read, datastream.h:231 + fn read_leb128(&mut self, end_byte_marker: u8) -> Result { + let mut value: u64 = 0; + let mut shift: usize = 0; loop { - byte = self.byte_stream[self.curr_stream_offset + idx as usize]; - if byte & UNSIGNED_END_OF_DATA_BYTE == UNSIGNED_END_OF_DATA_BYTE { - break; - } // final byte - read_num |= (byte as u64) << (idx as usize * DATA_BITS_PER_BYTE); - idx += 1; + let byte = self.read_byte()?; + if byte > UNSIGNED_MAX_DATA_PER_BYTE { + // Final byte. wrapping_sub mimics C++ unsigned underflow, so the + // signed variant sign extends and narrowing casts stay congruent. + let tail = (byte as u64).wrapping_sub(end_byte_marker as u64); + return Ok(value | (tail << shift)); + } + value |= (byte as u64) << shift; + shift += DATA_BITS_PER_BYTE; + if shift >= 64 { + bail!("LEB128 value exceeds 64 bits at offset {}", self.curr_stream_offset); + } } - - self.advance_pos((idx + 1) as usize); // advance the stream position - - // Same wrapping trick for the final byte - let final_chunk = (byte as u64).wrapping_sub(sign_marker as u64); - read_num |= final_chunk << (idx as usize * DATA_BITS_PER_BYTE); - - read_num } - pub fn read_u64(&mut self) -> u64 { - let u64_size = std::mem::size_of::(); - let num_slice = - &self.byte_stream[self.curr_stream_offset..self.curr_stream_offset + u64_size]; - - let converted_slice: [u8; 8] = num_slice.try_into().expect("Slice wasn't 8 bytes long..."); - - self.advance_pos(u64_size); - - u64::from_le_bytes(converted_slice) + /// ReadStream::Read(), datastream.h:153. kEndByteMarker == 0xC0. + pub fn read(&mut self) -> Result { + self.read_leb128(SIGNED_M) } - pub fn read_u32(&mut self) -> u32 { - let u32_size = std::mem::size_of::(); - let num_slice = - &self.byte_stream[self.curr_stream_offset..self.curr_stream_offset + u32_size]; - - let converted_slice: [u8; 4] = num_slice.try_into().expect("Slice wasn't 4 bytes long..."); + /// ReadStream::ReadUnsigned(), datastream.h:99. kEndUnsignedByteMarker == 0x80. + pub fn read_unsigned(&mut self) -> Result { + self.read_leb128(UNSIGNED_M) + } - self.advance_pos(u32_size); + /// Raw fixed width little endian, header fields only. Everything inside the + /// clustered body is LEB128. Renamed so the two can't get mixed up. + pub fn read_raw_u64(&mut self) -> Result { + Ok(u64::from_le_bytes(self.take(8)?.try_into().expect("take(8) is 8 bytes"))) + } - u32::from_le_bytes(converted_slice) + pub fn read_raw_u32(&mut self) -> Result { + Ok(u32::from_le_bytes(self.take(4)?.try_into().expect("take(4) is 4 bytes"))) } - pub fn read_byte(&mut self) -> u8 { - let u8_size = std::mem::size_of::(); - if self.curr_stream_offset < self.byte_stream.len() { - let byte = self.byte_stream[self.curr_stream_offset]; - self.advance_pos(u8_size); - byte - } else { - panic!("Reading past the end of the snapshot, something went really wrong."); - } + pub fn read_byte(&mut self) -> Result { + Ok(self.take(1)?[0]) } - /* - Panics if it isn't possible to create a stream from the utf-8 representation stored in - the byte slice. It shouldn't happen, so the best possible outcome is to assume some - logic mistake has been made and end the application. It should be a good thing to change this to an - unwrap_or_else so that we can also print the stream offset and cluster where this error occurred, in order - to have static debug info. - */ - pub fn read_c_string(&mut self) -> String { - let first_nullbyte_pos = self.byte_stream[self.curr_stream_offset..] + pub fn read_c_string(&mut self) -> Result { + let nul = self.byte_stream[self.curr_stream_offset..] .iter() - .position(|&b| b == 0x00) - .expect( - "Reading a string until the end of the stream? Something definitely went wrong...", - ); - - let raw_str = &self.byte_stream - [self.curr_stream_offset..self.curr_stream_offset + first_nullbyte_pos]; - self.advance_pos(raw_str.len() + 1); - - String::from_utf8(raw_str.to_vec()) - .expect("Couldn't turn null-terminated UTF-8 bytes into a String.") // it should be horrible if for some reason a string just isn't there + .position(|&b| b == 0) + .ok_or_else(|| anyhow!("unterminated C string at offset {}", self.curr_stream_offset))?; + let raw = self.take(nul + 1)?; + Ok(String::from_utf8(raw[..nul].to_vec())?) } - // read a non null-terminated string given a length - pub fn read_string(&mut self, len: usize) -> String { - let final_pos = self.curr_stream_offset + len; - let raw_str = &self.byte_stream[self.curr_stream_offset..final_pos]; - - self.advance_pos(len); - - String::from_utf8(raw_str.to_vec()).expect("Couldn't turn UTF-8 bytes into a String.") + /// Dart OneByteString payload is Latin-1, not UTF-8. See the string cluster comment. + pub fn read_latin1(&mut self, len: usize) -> Result { + Ok(self.take(len)?.iter().map(|&b| b as char).collect()) } - /* - Complex object types (i.e, object types that contain other object types) - point to other objects through refids, which is essentially the core - mechanism of Dart's serialization/deserialization process, allowing the - reconstruction of all objects from the snapshot into the heap. - */ - pub fn read_ref_id(&mut self) -> u32 { - let mut idx: usize = 0; - let mut byte: i8 = self.byte_stream[self.curr_stream_offset + idx] as i8; - let mut ref_id: i32 = 0; // as far as I know, ref_ids are up to 2^28, so 32 bits is good enough - - if byte < 0 { - ref_id += byte as i32; - self.advance_pos(1); - return (ref_id + 128) as u32; // same as ref_id & 127 - } - - loop { - ref_id = ref_id << 7; - ref_id += byte as i32; - idx += 1; - + /// ReadStream::ReadRefId(), datastream.h:103. + /// Big endian VLI, not LEB128. Dart caps it at 5 stages (28 bits). + pub fn read_ref_id(&mut self) -> Result { + let mut result: i64 = 0; + for _ in 0..5 { + let byte = self.read_byte()? as i8; + result = (result << 7) + byte as i64; if byte < 0 { - break; + return Ok((result + 128) as u32); } - - byte = self.byte_stream[self.curr_stream_offset + idx] as i8; } - - self.advance_pos(idx); - - (ref_id + 128) as u32 // ref_ids are always unsigned + bail!("ref id longer than 5 bytes at offset {}", self.curr_stream_offset) } - pub fn read_bytes(&mut self, len: usize) -> Vec { // copies the data - let bytes = &self.byte_stream[self.curr_stream_offset .. self.curr_stream_offset + len]; - self.advance_pos(len); - bytes.to_vec() + /// Reads a block of bytes and returns a newly allocated Vec (Creates a copy) + pub fn read_bytes(&mut self, len: usize) -> Result> { + Ok(self.take(len)?.to_vec()) } - pub fn read_bytes_zero_copy(&mut self, len: usize) -> &'a [u8] { // borrows the data - let bytes = &self.byte_stream[self.curr_stream_offset .. self.curr_stream_offset + len]; - self.advance_pos(len); - bytes + /// Reads a block of bytes and returns a reference to the slice (Zero-copy, highly recommended for large payloads) + pub fn read_bytes_zero_copy(&mut self, len: usize) -> Result<&'a [u8]> { + self.take(len) } } diff --git a/crates/flutterdec-serwalker/src/utils.rs b/crates/flutterdec-serwalker/src/utils.rs index 92b9c71..6338335 100644 --- a/crates/flutterdec-serwalker/src/utils.rs +++ b/crates/flutterdec-serwalker/src/utils.rs @@ -23,12 +23,12 @@ macro_rules! DECLARE_FIXED_LENGTH_CLUSTER { impl Cluster for [<$name Cluster>] { - fn read_alloc(&mut self, last_ref_id: &mut u64, stream: &mut Stream) -> usize + fn read_alloc(&mut self, last_ref_id: &mut u64, stream: &mut Stream) -> anyhow::Result { self.start_of_alloc = stream.get_current_pos(); self.first_ref_id = *last_ref_id as u32; - self.obj_count = stream.read_modified_leb128(UNSIGNED_M); + self.obj_count = stream.read_unsigned()?; for _obj_idx in 0..self.obj_count { @@ -38,10 +38,10 @@ macro_rules! DECLARE_FIXED_LENGTH_CLUSTER { *last_ref_id += self.obj_count; self.end_of_alloc = stream.get_current_pos(); - self.end_of_alloc - self.start_of_alloc + Ok(self.end_of_alloc - self.start_of_alloc) } - fn read_fill(&mut self, stream: &mut Stream) -> usize + fn read_fill(&mut self, stream: &mut Stream) -> anyhow::Result { self.start_of_fill = stream.get_current_pos(); @@ -52,7 +52,7 @@ macro_rules! DECLARE_FIXED_LENGTH_CLUSTER { $_self.end_of_fill = $stream.get_current_pos(); - $_self.end_of_fill - $_self.start_of_fill + Ok($_self.end_of_fill - $_self.start_of_fill) } fn is_fixed_len(&self) -> bool From fc12872cd0a608b30b2fa2bc01666938377d4e43 Mon Sep 17 00:00:00 2001 From: Kl <64610615+kalixtez@users.noreply.github.com> Date: Sat, 25 Jul 2026 21:37:46 -0500 Subject: [PATCH 32/50] Update crates/flutterdec-serwalker/src/snapshot.rs fix(serwalker): drop non-existent kModule snapshot kind Snapshot::Kind (snapshot.h:24) is Full, FullCore, FullJIT, FullAOT, None, Invalid. The invented kModule shifted None and Invalid by one. Co-authored-by: Camilo Vera Vidales --- crates/flutterdec-serwalker/src/snapshot.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/crates/flutterdec-serwalker/src/snapshot.rs b/crates/flutterdec-serwalker/src/snapshot.rs index abd813b..0f6e6c7 100644 --- a/crates/flutterdec-serwalker/src/snapshot.rs +++ b/crates/flutterdec-serwalker/src/snapshot.rs @@ -7,13 +7,12 @@ use crate::utils::{decode_tags, DecodedTags}; #[derive(Default)] enum SnapshotKind -// pulled straight out of the C++ def +// Snapshot::Kind, snapshot.h:24. There is no kModule variant. { Full, FullCore, FullJIT, FullAOT, // Full + AOT code, this is the one we care about, as this is how flutter builds projects - Module, #[default] None, Invalid, @@ -28,10 +27,9 @@ impl TryFrom for SnapshotKind { 1 => Ok(SnapshotKind::FullCore), 2 => Ok(SnapshotKind::FullJIT), 3 => Ok(SnapshotKind::FullAOT), - 4 => Ok(SnapshotKind::Module), - 5 => Ok(SnapshotKind::None), - 6 => Ok(SnapshotKind::Invalid), - _ => Err("Invalid snapshot kind... Either headers are corrupt, or this is not a snapshot at all."), // Handle invalid snapshot kidjns + 4 => Ok(SnapshotKind::None), + 5 => Ok(SnapshotKind::Invalid), + _ => Err("Invalid snapshot kind: header corrupt, or not a snapshot at all."), } } } From 4bf529fdb96d3a33e75db823722016b2da2b0bfd Mon Sep 17 00:00:00 2001 From: kalixtez Date: Sat, 25 Jul 2026 21:48:08 -0500 Subject: [PATCH 33/50] fix(serwalker): surface unknown class ids instead of defaulting to IllegalCid --- crates/flutterdec-serwalker/src/snapshot.rs | 59 +++++++++++---------- crates/flutterdec-serwalker/src/utils.rs | 17 +++--- 2 files changed, 43 insertions(+), 33 deletions(-) diff --git a/crates/flutterdec-serwalker/src/snapshot.rs b/crates/flutterdec-serwalker/src/snapshot.rs index abd813b..8470a2e 100644 --- a/crates/flutterdec-serwalker/src/snapshot.rs +++ b/crates/flutterdec-serwalker/src/snapshot.rs @@ -63,47 +63,49 @@ pub struct DataSnapshot { } impl DataSnapshot { - fn parse_version_and_features(&mut self, stream: &mut Stream) { - let mut version_and_features = stream.read_c_string().unwrap(); + fn parse_version_and_features(&mut self, stream: &mut Stream) -> anyhow::Result<()> { + let mut version_and_features = stream.read_c_string()?; self.features = version_and_features.split_off(constants::VERSION_HASH_LENGTH); // returns (str[hash_len..]) self.version_hash = version_and_features; + Ok(()) } - fn parse_header(&mut self, stream: &mut Stream) { - self.magic_bytes = stream.read_raw_u32().unwrap(); + fn parse_header(&mut self, stream: &mut Stream) -> anyhow::Result<()> { + self.magic_bytes = stream.read_raw_u32()?; if self.magic_bytes != MAGIC_BYTES { - panic!("Not a snapshot...") + anyhow::bail!("Not a snapshot...") } - self.size = stream.read_raw_u64().unwrap(); - self.kind = SnapshotKind::try_from(stream.read_raw_u64().unwrap()).unwrap(); + self.size = stream.read_raw_u64()?; + self.kind = SnapshotKind::try_from(stream.read_raw_u64()?).map_err(|e| anyhow::anyhow!(e))?; - self.parse_version_and_features(stream); + self.parse_version_and_features(stream)?; - self.num_base_objects = stream.read_unsigned().unwrap(); - self.num_objects = stream.read_unsigned().unwrap(); - self.num_clusters = stream.read_unsigned().unwrap(); + self.num_base_objects = stream.read_unsigned()?; + self.num_objects = stream.read_unsigned()?; + self.num_clusters = stream.read_unsigned()?; - self.instr_table_len = stream.read_unsigned().unwrap() as usize; - self.instr_table_offset = stream.read_unsigned().unwrap() as usize; + self.instr_table_len = stream.read_unsigned()? as usize; + self.instr_table_offset = stream.read_unsigned()? as usize; + Ok(()) } - fn parse_clusters(&mut self, stream: &mut Stream) { + fn parse_clusters(&mut self, stream: &mut Stream) -> anyhow::Result<()> { let mut curr_ref_id: u64 = 0; // all objects are numbered starting from 0 self.start_of_alloc_area = stream.get_current_pos(); for _cluster_idx in 0..self.num_clusters { - let tags: u32 = stream.read_unsigned().unwrap() as u32; - let decoded_tags: DecodedTags = decode_tags(tags); + let tags: u32 = stream.read_unsigned()? as u32; + let decoded_tags: DecodedTags = decode_tags(tags)?; let cid = decoded_tags.get_cid(); - let mut cluster = decide_cluster(cid).unwrap_or_else(|_| { - panic!("Couldn't find cluster implementation for class {:?}", cid) - }); + let mut cluster = decide_cluster(cid).map_err(|_| { + anyhow::anyhow!("Couldn't find cluster implementation for class {:?}", cid) + })?; - cluster.read_alloc(&mut curr_ref_id, stream).expect("Failed to read alloc for cluster"); + cluster.read_alloc(&mut curr_ref_id, stream)?; // Composite key exactly as suggested by PR reviewer let key = (cid as u32) << 2 | ((decoded_tags.is_canonical() as u32) << 1) | (decoded_tags.is_deeply_immutable() as u32); @@ -115,22 +117,25 @@ impl DataSnapshot { self.start_of_fill_area = stream.get_current_pos(); for key in self.cluster_order.iter() { let cluster = self.clusters.get_mut(key).unwrap(); - (*cluster).read_fill(stream).expect("Failed to read fill for cluster"); + (*cluster).read_fill(stream)?; } self.end_of_fill_area = stream.get_current_pos(); + Ok(()) } - fn parse_roots(&mut self, _stream: &mut Stream) {} + fn parse_roots(&mut self, _stream: &mut Stream) -> anyhow::Result<()> { + Ok(()) + } } -pub fn parse_snapshot(stream: &mut Stream) -> DataSnapshot { +pub fn parse_snapshot(stream: &mut Stream) -> anyhow::Result { let mut snapshot = DataSnapshot::default(); println!("Now parsing the snapshot..."); - snapshot.parse_header(stream); - snapshot.parse_clusters(stream); - snapshot.parse_roots(stream); + snapshot.parse_header(stream)?; + snapshot.parse_clusters(stream)?; + snapshot.parse_roots(stream)?; - snapshot + Ok(snapshot) } diff --git a/crates/flutterdec-serwalker/src/utils.rs b/crates/flutterdec-serwalker/src/utils.rs index 6338335..0616db0 100644 --- a/crates/flutterdec-serwalker/src/utils.rs +++ b/crates/flutterdec-serwalker/src/utils.rs @@ -133,10 +133,15 @@ macro_rules! DECODE_IS_CANONICAL { }; } -pub fn decode_tags(tags: u32) -> DecodedTags { - let class_id: ClassId = DECODE_CID!(tags).unwrap_or_default(); - let is_deeply_immutable: bool = DECODE_IS_DEEPLY_IMMUTABLE!(tags); - let is_canonical: bool = DECODE_IS_CANONICAL!(tags); - - DecodedTags::new(class_id, is_deeply_immutable, is_canonical) +/// Bit layout matches UntaggedObject::TagBits in raw_object.h: +/// ClassIdTag at 12 (20 bits), CanonicalBit at 1, ImmutableBit at 7. +pub fn decode_tags(tags: u32) -> anyhow::Result { + let class_id = DECODE_CID!(tags).map_err(|_| { + anyhow::anyhow!("unknown class id {} in tags {tags:#x}", (tags >> 12) & 0xFFFFF) + })?; + Ok(DecodedTags::new( + class_id, + DECODE_IS_DEEPLY_IMMUTABLE!(tags), + DECODE_IS_CANONICAL!(tags), + )) } From 7ded3ac48b994ad3ef95a2c414fbec30ae0924ff Mon Sep 17 00:00:00 2001 From: kalixtez Date: Sun, 26 Jul 2026 22:21:35 -0500 Subject: [PATCH 34/50] fix(serwalker): fields that aren't present in precompiled snapshots were removed (commented out) from multiple object definitions, some fields that weren't being read were added (and read in the respective cluster definition) for multiple objects --- .../flutterdec-serwalker/current_clusters.txt | Bin 0 -> 40402 bytes .../flutterdec-serwalker/raw_object_dump.txt | Bin 0 -> 24688 bytes .../flutterdec-serwalker/src/cluster/mod.rs | 82 ++++++++++++------ .../src/raw_object/mod.rs | 73 +++++++++++----- crates/flutterdec-serwalker/src/snapshot.rs | 2 + crates/flutterdec-serwalker/src/stream.rs | 2 +- 6 files changed, 111 insertions(+), 48 deletions(-) create mode 100644 crates/flutterdec-serwalker/current_clusters.txt create mode 100644 crates/flutterdec-serwalker/raw_object_dump.txt diff --git a/crates/flutterdec-serwalker/current_clusters.txt b/crates/flutterdec-serwalker/current_clusters.txt new file mode 100644 index 0000000000000000000000000000000000000000..536e6e72d57574a0232b5575067620c4dcf7e707 GIT binary patch literal 40402 zcmeI5X>T0I5r*e;fc%HG5!jR*F>(?Zf)5#%WZFb@L8P1nhC%ZdEmCAi$`T{#uP1q* zt}XTS>>NA0J+vGz2x<11?W*psTkDrR{R`Z{akDo zr^RQ*?~C6RllSq-LGf?hZ+DdM+Sczj)#7ewXS+C7n^4%)^<1x@^QyRA9O{!Z{jG}j zAH_xb33pu$pRehTZ;JcH*TtP;qqrA7d#v}>&)-zIP5FSQ7yA8iu~qye-nPYy(MbE4 z{tCWNb@yg*6z*Oty>I*79dUcRxLbTxJkqO;Vnu!U$G8V~b-k}1try?w8}53nzO3uh zN3A`mw)tIXeO3S8QX5d2wdIw---dYjXYo)it%bH$16Of=Pb3FhnkPnNQ?q|Qo-<^D z**?|#S3zDzdd1(KW_+YO&(z+Yt}k`hj$R+?`m9(gKF~M2dbd|x(S6s7oBDrCGjy!a zjBeVJ%>*rM>W)L*w;ftJ5@&GZtszY&UFW)&U+w975M+ELs(%0T;*;X9L7viiF5gq0 z@yI2zd{I0R2QPwLJQ9sFad9XrI$1wAW|X<}8l% znfN*oe|b4s3#~HWsl*@|%%p3pQ6CjwgxQU{xEyA6Q!~0G+4)kVJSzUAKV$%Y&-4oW zg>^$#Ug-ae@aYSEitHk>NGCRkUmX)JQdX{ht`hu z>q@vkj_r}CU{{WVG$JKP%X@0kHSe~xcrEDgL%mubTZ8+$BORY5eewrU=l{K+Qyc2Z zg=PtCydQ%TJG@;S$% z2>*;tnG54Nj>Sm#n_coY%OiDV%*p#f#_ITSsjl-U@2PG7zDeqlgDUsy;(nx2Iqsys zQSyjeo-Q>dT&3X8Yd5uVedFFE@kFhlWHIl~%TkL4&-Jh*! zpO=W6Ak z_?Kwnn~_WO+O!zk>Q-_qeCIguSkk&wJSg>lzzu@(uJI9k8)8>-kc5F&CradeDg2-tu^ivB1ZgMe_&dpCWFr(r{bi z9@uD^=Z)CpYKd>Bb5?x+mLittW${A=^?2LX=CU}l5kV`OJ$s7W+|Sq98*g_{^6Ciu1jDoY(N#GY)BG@@z8kD<6zYegozB_lFu2YzgwZN#Csk^lToVmJN-OKgh|0=cKEaWkb4ZUaFm5 zmww*p@P(dPi{tINP|pY3T3&Viz&-szG3jsfS^&3H3!mp0vt1(kYQa!(jIXlTN~U?6 zyqu#ShRxAZ!^T7!Rb9$U)iT6jjxuKTC?|SW%R1(zy)XPh>_Plw>%wH9;e{+D*=};? zd1+!3!46RIIn7If-hkY|_9pF_Bg1#`WNSR zLv!FW4$@+c4!=*_0p8BZ0d2Z(pS_=x9%wSw^oY+Q+^)QFmsg#nnA7L2N!vV^IlI?m zRgYfJv)+-jS9itDRzUKn$^^~cFT=h6KWjQ|`qY0#hdjYytB&?>lbrW~s@zQfL8iN! z!MuF$h(e63u?o2<*oq=+$Cg9M%l~=EKH!Ca)<3d@L#=eMB5B^gyynqm(fXo7G7*dw z7Gz(&!6I5cM$YJmoMySQBWK%qvD|Ij%DYG2Rop@)5B{>hWR0{U{JrW;9w^r_(i%85 zkW@F_5ARoXrSge2&qGx{aMus|t6SMkIZW|RzO}2N_;snvBj5SB^cnZcjOpbz#bnP_ zYw<}3Dw}RizS?dR)@zdoVy5Vk4SOQfR7$jI;&kXJ|LD7Pn_15WOIqf6IQ^>P zeOtG`5P~vfn|u5w>DfB^$$0j1(;E`~+v=+2e30qfyic}o>pWYUo%Re<|LC=hatxmn zo8hCWM9%A%V$25;$VVrqjejidV@#fnSA%bBEQl)>mfftA*$J!UL@L>XaujS9mY!Os z6G@Kkm}s-~O(^-jfUU1rqJW$;ys8}yi}mrcU-Qh>uOa(;PgaS2DXFZXrDm}fdg?nf zEK6Xhi+rI~#i4iYHDqb*JoF0on({VV;>h+ltZR1htBd*c{XXn&U~ib^AJ`w)jW?QX ztMA#ET%W0~2susm&1{P%C=GdP#{>Q8=Ctc~$H>(%`eTh78QFzBDA(w<&tKo@i>es1 z?dghaZZtewZC=%iu`@ee@69WFL~gcpoPBu{y&FI@RyC7qtx| z_LawS%$HZvEq8e)d1rR>wr#udY!@*V1XQjJNpG6UxvKUw<!Nll4r?-d`iFlt zPvZI9_C7YzAKv=<&h~{KS*@R)1Ck@FoYVeMT~XfIvzRqUKAyR#+xOU|^?mUz_p{fH zQ=wu-YP)9lrD4&ct=2~jXW5zD{c+C$;;p0W-{+>AMwE4wS%E>~FC*8Ia?>VHkTQ#urk@ti@^uP61FGjOJNMw1>UFz*_8n8JBLT&63P9er@iO?D`C84X)pr7B zvl^L&PO~3{Q!O0_?1i&wIiYw@noX?+XU?!fcBa!w(0-%~+#mNclUcD-j_t$}%L@Bx zU~cylblO5Pqz4$`@T>d}-!_@eeYm|LIv!DIdKNh?!nPElDvO(lz4&TP~ zQHExhop;Svuc@b0lRZ^0vCE6yDnAJmfc7{on^SJQK6XCr=lH?1BcN5=Q$k+AJYrr7 zCxQD^1Bapo<|eaeC#ex(*{Bg=wD+nh3Lkzg=8?R>epvlJS@;6U3VAd?Wisx>pgN=b z1i-$r8nR+f5nzW=yM**@n;j)oX}O=t+xoeX16AO|v|i=ib~jRA{#5p8*q#hYYr1`` zwzJ$%6~lIxBOLO_U5R(-^qz+zn=rS ztJlxQ+PWcYzdff8Ripp9lkWRHS%~xQ?*$9%nTG1Qb;%*J3&U@=%tIlQZ+RPPCNP%6+SG_xeJf z0hEu{ZShV%-ZXrub~$s&?0-HA_mp*j^L#C?XcpPMdLFzY8N?ylrjq=nGL-q+26r>Q ztd{3%HRcubwdr_Zh_+XStL>R(o+TZEis=isJGYOCE$8+&v`l2YrYk2{VOxgazWUs$ zmufLZui-g?O+Ba2JRxyey|?oP{j5%M?*7!My12^D7^VgZ86*b9dQ-W@socaR z>HZqe??j5!zu^7&RXK(=YF9-abTauZpN>Q|?prI*(Ig$qD3ziPo*oZ;0JW7YS@ zW!v{W)t|H%`#i}@9q*~cdyo9essih`{zOM>)B0r3vSVHEbLrtU`gSusVG?^1pA^(Q zd(MN=T-F>h@6(*5+LXtdZZ@56btKOux$J^u?RiQ4WgV?a@+S4!TWyxLkH%*^xt2`M zU%gyJJ{cdRiacX3ovM==dv4@3bgxNv;(cv;Z%>-^QJNgTsy+0*#+U!R(OK-9b6aN( zX0>i^4_r>{iDl{8qH*l~u5C#N`f;B4A0PMWtbw&HR=4x1tbDWs zYqBk-e@F6<{xoc~cDmoMj=jaV5)akJMjCT=3HtX4JxY4m|YI)mseb?j0 z=FxOZTyNu{l+t#qAAOFuP`yUmZf(CS*t1TiBCn|*8b&AEy-=BEmmiAv)_J0GPZ!v; zJQ^;mZc9u;FEbiVwQ3fP9*v*XY*nblD4XGYa4IBNRc4hp&CqzS{p!|*l{3zP^voeE zw}x$}t>Y@A#k6N`FTBNw66@xp%9+i}Wuo*H9Zxed^$~4Ef!-&-wrjs5OE5=!-064L zX?~>68O`(K&<8?3yTvV`AJ$3?9kKqfAF>A{)q2Gzud||JPbz0s#hxKEl00$;wHcg5 zPS*2*vao&{Ip>kzQM~_9pK>Dky52t$9b_a=57tASMNxEV$b+hM4r?sP`)tzTng@NPvh&Yxc+pF>12b@SAY zV`5~|jb!4zby%iMdlAKyN5kjdt?#T+OqVvh?d9yb`7NTyEg(W_06~aEES!7HD{vyY zw{jHorQUt3od1*J%T_z*oR;H;s2Nd%eZ9iGiD$TD{5Ki3blajy+I4f~bLLm$X*sNC zyimWXjmC?zf0$J?xL+z>>u;oQ$UqX;QRVl3Sh3_hlO28fp+=BZ-e00xVwx`vkEtMk~8{c?_zf0!b&_BlNntU!hb$H4Ei9gBs$mMQuX)H{Y>A-eXE>- zyQlj`p>5ws={mTpc9(l`>T>VM42ddZ6>=NpaRzmeoj~KMGN2di6XKJL9;;2Jkv?8s zSGSSX{Zg)f^dhdz_meEAo%X!E(r)@NxkK3VCTU$dy2Q*kt;{>U>eZtD>ZeWbz2``F zd3T!C?fbcIX`iMW^Xd37nP|qM^Jw{|WG+RvWeLQ`n1#poA$#ZJjKz<-MmhLVt#Fpc zHPuNVoujvMZI;$DZ+*17sw=K1@fmpNAsX{t(4xG$(udQ_&=<3DzRo>TRp{vLJ{ZPf zNwdKiGdsJQ1w*#vDW6F<`t-(iz{h`U96h;ToK`d2p0VaMny!&H{cOwl)?0-fv?t}w zII-~nV=QR~;O}$&jpQZV2F)_slho2Rpvmu@TGJR-KaY9N>NidAP3Cxl1$%LP{tXu) zb9hxyDbD}{S(&Eep{TyIJOHQ2QNza*tUyw%&*Njsk@AF2Y-hx3pd#{~_`Ckzbvi3u zwr<#rs<+3AvGbH#Lw2H4aWpylv8teH^!)R73t#_j^IS!QZW`{@U!LKv(-ueYed{UP Mqt-}q{_Okz08;eK%>V!Z literal 0 HcmV?d00001 diff --git a/crates/flutterdec-serwalker/raw_object_dump.txt b/crates/flutterdec-serwalker/raw_object_dump.txt new file mode 100644 index 0000000000000000000000000000000000000000..00172a581a56c2512fc87fd5adee8e9fde15b84d GIT binary patch literal 24688 zcmd5^*=`)i747E$`3J&zNem-K76gH_09q_ZFeMWq<2(cg!JT7q!;o5-AitjEoIZ1P ztE%hHR1bR?fsne%>8`u4b!-0npSRs@chYU)|GVzE`wBnD`2Dmy=-zf0_}d@4pLRd# z|J~p(hj{X?yYFuLcXqqWZic`73IAr@Z@OP6Pyej@N59e>pS;7;9tqAzPL3 z=YVj8>sI$2ApMBv_1nMtC%5o-{x}{7-5K!y3V(as{jU3M_eKBQzx)4*^}}#@2Ko=M z{w>z8V{nDfFM*{nApKwT7#gPQT)*tT#2ULjH)h=xDBS7qNsp~(9FXkh-9Db13X?JL zPr>)AUPiO-8h0j(hj2phpFkJppy_@I^+V|U-FYvub8zGicZZPRIO&XmKEv7<(D5m# zmSo$ZU&BIA0r>=U?!&^a@c(aX#aQ+y&ETQsyCI^o@6<$3QXj&S#I9<)Qh zg~rjV+yI9by3A^pT?KtNSvj`43&1|?wQ~;roCX8I-_BN!F)$Cj4;~!!n9vJJBI`;* z80;;4`%Y!u7@u4C0Y(IRBE#*Wd@1o^bg22jqlM?8y;CdKr9agU!EW(EjFm@yoS?V5 zgZ$_RAI61N%4vn~0PiS+d<-0Jd%d{BC+7>P@EQHKTrOiMpFtvu(DXo;fJtvlO{5&? zi^j@A(7(Z(*YIZMztdcxO(rR3)Xl?%j2O$;^Hoz}R@W9vlKBrcnBLU&XCHr?BL{f< zk*_6Bb8R7i@~2~~J%GF}2Kqxh$g$;ml3Gi8FKnJ+Ra!<2o1fBST=maUU-m({-dYdx z7V;)$PJhR$2i=>lMN_VNzAAg7?J`eIqo(otSI91vTNkXcsgK|dZebDTYJf*z$rr~QoHqwq+7h`-5h{Rf1L#}nVpx~c9EoDYQ%#`8R`QWQA z!!6})PWdp0X@^Koe&{LkeCMI7(-vt-o?lJHseI+f;#LlI3XV-xWiiuGq*`5Xy@yN= z;5Xw!AU7`y0@54GkkjmkVn=#b0ttwEnlC z*F45f_TEeQ5x(VAO(mBX@QrD0x49bFc*qQoerH+5%e;WvJ1_W@%Y4~yf|lkK&O3Y0 zz55K!c!?_-u7WwpmW%!ASJ9FsA0&R|LVuqe`bfIIp=Rp0cZ z%<2z;F{4}*qsKst)^OZc-3i+2EaL{)Gp1Qy&Wxv4GkO1{^dy4Rl1tE9uHl&oi(N6s zQTeFWtfrMJc{&fI4xOX(Drzsr(GRKBXl4dJb5G z7kL}%j(QJ@`1IN2dd)JOpZ*3twVscyUNyC<7-lTFAIv>~V3hV9ug!?%)aGlM?Cfgz zr*Fh76YC@7z0x|*gmR6ZQgScAn_0KldkR*6*PxC5`vjfd#;XkX3vJqGy6^=0L|tH3z&)AOx=*<)@=_la51C1nI#R=9tj>G&nMOFv zU7TiCnWl@jM`7|}%#JmjJr!m}>{~D76lTQB*W^>? z?`ya$>k`Rx#(kx!{9#(XmLAb8(L5%#T|1&)drz5DQVw5~txsxz3fJpl=<)N(x*3MuD_Yp%ZPLY9@}>6 z9!h(ltb3v;lE&t`6`K8}RL6FAiwuzc$tk)?=ioFsJH=Y7d#20joJum^f}Z7F>_}3% z7P5n*egpF{_I66?i5{y_jz9Jzp7!x!*}kM|9P`m>HBpZd5u{~W;u(ndTuTbfhgb|4 z((--p8Q(9dSy)xGC&@UY3Om*&!W&OZnF*-3dJo^CSXc5ag0PyFAtd!bZ$KO4>2#3V z{BtRvmdr-IOH~fk+7f49Q+MLldMm5b6wSY_FNZV&C^9oYi)Ty5&|ujcefRQJU0ZA1 znaW>1gBH^V@8ZgefU)EiXuTOk6jAAYrA=2!#Ec%09X!pn9QPBW>HrJJcxpB3Sk&US z=Ly^uVkb}CRPJu1KCAX_S|5Cm=a0HS_C3HqPdcT$gLR+xeDHi;vqy|3=GEv2Iaf@M zsXuyOohBQ@o5!K=>p}PSFAGT!SI$^PQIoT*Mb> zQ0ILgC!#ssyGixaXvii>D^`!NbIIIY z6MM`JtPVW{Ozz@LJ4erom2u;0u{-q)+~f{LM49pyejW|lcKQ^+SHBRr|8o~6(}Z0-KCj4L`hQ#&o9HT8MypX{6i zeO$bj=Si#9qc@>V($CFsrAJWDuw1>4-&8r5Noxm~Xfn#?+9PR2)vfT%Etr*8EmPrM zDJO#!G2*TXH6!hJ_>)u9A5lxLm7>&bvlUjqy-oab{xu`_i7q);*q`=3E%N z4x*+Pw;b$JI2W^#79m*(-YtI zr-FF8kNf|!DehY7yq}%7qoj0F;H3K{?w{g`JNz)ev^9SOhv*ek`=J+Nm!8k?{Vl}k zUEJHw_ZFhFRdri+s*hP6&S2IwD9HN`A~dUm$Z5VPl;o6}~; zrN!iWV>x?Dh3`!(`jbB6Wi*{n-e^7{kLS|MRoOwDy|legp308t`MIYjdK`aoeOk^k z`e}-_)G*`f9W0l#gV8yPvNAR?DGX^8(ojqySIxpS#n_W2=4QsE(#XR`Z6E zqr4=w;n^VJ&H9!8+A|EJ%Q%tdtj<|`D!tY@VNck4jAx7_(w#H|NTY;3UC$NxZJ4-<+_SHwt6k^ie;dVp-%SaexyYrkq*W1hLkzfG9OB*6pe1d zF$Bvc+2N`|v(HOt?GU)CPuA98uvf$UvFz#B^e|t|Jy6@a(^njvn(&F_NLOiN^a0G_ zSPio#W)H@txk(>d&J2!$YFd?B%@_|(uf{C)9@n&wx|(8xZ9NXN*)h;d+GD@q(8@c} zUizolrl`-}Yh4GTQQTI4w?~GOGXGsPmYK{{zWrsQ|FxL{jwPQ_b&u&a3HYFjmt>JEe#F-fPx-OZnSw=yR9p@iYY2J{o zlAg!by&*=ne_vu|oVr`&+4P?EohRe1X~_`A>e-ESTEk|6y$?Vy=Cw~gS)tlJW+eReysZX2Tj4)FU zSf6Qf9*TSQm19qBJ#J2+`+WRVS+Sq7(2BDSd>_}#?%Bslu84z?Y%Ru3S?|4q2Ax5= z`EDe4H}y>`5su^)_ga)qO4^*z=FE%L5%gA@(8pcWDTm12)%Vx~4&>NsoNc$6bpXA3 zOQmD)@EdB5@D69SNOPJ`PAQLVaGt$8p48ztH+&OD;eu*7S#OTeigQMr0`Tig4f@!I?@fqB1wp#DE2_A+>5 z)=tj?#;0)Dp|JUKM!-|(8l}RT@4DaB&}qfZ?D*3|vl{Wanc@6Rq)p7T^K+J^Y{ady zmWZVxZ^qsOWkL&X`(eKu#| zXUKU7JCYVB>uw-jebw)dd#=O)nt$+VaBzvtr zURIiq*xdRh{@O$1)Z!c1O-aqe@t=DA&F`97w~;x8CSG~6@IKUG&b16VXS$VN#m}}L zbUrs}^T5GX4$2zEMtoDVac-G9Wjq%Z$HkS!%O4?m&g*BscEL$ Lv#OG=aXR=v#PvP! literal 0 HcmV?d00001 diff --git a/crates/flutterdec-serwalker/src/cluster/mod.rs b/crates/flutterdec-serwalker/src/cluster/mod.rs index 652e262..9dbb92f 100644 --- a/crates/flutterdec-serwalker/src/cluster/mod.rs +++ b/crates/flutterdec-serwalker/src/cluster/mod.rs @@ -54,6 +54,7 @@ DECLARE_FIXED_LENGTH_CLUSTER!(PatchClass, |_self, stream| { obj.wrapped_class = stream.read_ref_id()?; obj.script = stream.read_ref_id()?; obj.kernel_program_info = stream.read_ref_id()?; + // obj.kernel_library_index = stream.read_unsigned()? as u32; [[NOT PRESENT IN FullAOT]] } }); DECLARE_FIXED_LENGTH_CLUSTER!(Function, |_self, stream| { @@ -63,12 +64,14 @@ DECLARE_FIXED_LENGTH_CLUSTER!(Function, |_self, stream| { obj.owner = stream.read_ref_id()?; obj.signature = stream.read_ref_id()?; obj.data = stream.read_ref_id()?; - obj.ic_data_array_or_bytecode = stream.read_ref_id()?; - obj.code = stream.read_ref_id()?; - obj.positional_parameter_names = stream.read_ref_id()?; - obj.unoptimized_code = stream.read_ref_id()?; - obj.bitmap = stream.read_unsigned()? as u64; - obj.kernel_offset = stream.read_unsigned()? as u32; + // obj.ic_data_array_or_bytecode = stream.read_ref_id()?; [[NOT PRESENT IN FullAOT]] + // obj.code = stream.read_ref_id()?; [[SKIPPED BY WriteFromTo]] + // obj.positional_parameter_names = stream.read_ref_id()?; [[NOT PRESENT IN FullAOT]] + // obj.unoptimized_code = stream.read_ref_id()?; [[NOT PRESENT IN FullAOT]] + // obj.bitmap = stream.read_unsigned()? as u64; [[NOT PRESENT IN FullAOT]] + obj.code_index = stream.read_unsigned()? as u32; + obj.token_pos = stream.read()? as i32; + // obj.kernel_offset = stream.read_unsigned()? as u32; [[NOT PRESENT IN FullAOT]] obj.kind_tag = stream.read_unsigned()? as u32; } }); @@ -100,13 +103,17 @@ DECLARE_FIXED_LENGTH_CLUSTER!(Field, |_self, stream| { obj.type_field = stream.read_ref_id()?; obj.initializer_function = stream.read_ref_id()?; obj.host_offset_or_field_id = stream.read_ref_id()?; - obj.guarded_list_length = stream.read_ref_id()?; - obj.exact_type = stream.read_ref_id()?; - obj.dependent_code = stream.read_ref_id()?; - obj.kernel_offset = stream.read_unsigned()? as u32; - obj.guarded_list_length_in_object_offset = stream.read()? as i8; - obj.static_type_exactness_state = stream.read()? as i8; - obj.target_offset = stream.read()? as i32; + // obj.guarded_list_length = stream.read_ref_id()?; [[NOT PRESENT IN FullAOT]] + // obj.exact_type = stream.read_ref_id()?; [[NOT PRESENT IN FullAOT]] + // obj.dependent_code = stream.read_ref_id()?; [[NOT PRESENT IN FullAOT]] + obj.token_pos = stream.read()? as i32; + obj.end_token_pos = stream.read()? as i32; + obj.guarded_cid = stream.read_unsigned()? as u32; + obj.is_nullable = stream.read_unsigned()? as u32; + // obj.kernel_offset = stream.read_unsigned()? as u32; [[NOT PRESENT IN FullAOT]] + // obj.guarded_list_length_in_object_offset = stream.read()? as i8; [[NOT PRESENT IN FullAOT]] + // obj.static_type_exactness_state = stream.read()? as i8; [[NOT PRESENT IN FullAOT]] + // obj.target_offset = stream.read()? as i32; [[NOT PRESENT IN FullAOT]] obj.kind_bits = stream.read_unsigned()? as u32; } }); @@ -126,13 +133,14 @@ DECLARE_FIXED_LENGTH_CLUSTER!(Library, |_self, stream| { obj.loading_unit = stream.read_ref_id()?; obj.imports = stream.read_ref_id()?; obj.exports = stream.read_ref_id()?; - obj.dependencies = stream.read_ref_id()?; - obj.kernel_program_info = stream.read_ref_id()?; - obj.loaded_scripts = stream.read_ref_id()?; - obj.num_imports = stream.read_unsigned()? as u16; - obj.flags = stream.read_unsigned()? as u8; - obj.kernel_library_index = stream.read_unsigned()? as u32; + // obj.dependencies = stream.read_ref_id()?; [[NOT PRESENT IN FullAOT]] + // obj.kernel_program_info = stream.read_ref_id()?; [[NOT PRESENT IN FullAOT]] + // obj.loaded_scripts = stream.read_ref_id()?; [[NOT PRESENT IN FullAOT]] + obj.index = stream.read()? as i32; + obj.num_imports = stream.read()? as u16; obj.load_state = stream.read()? as i8; + obj.flags = stream.read()? as u8; + // obj.kernel_library_index = stream.read_unsigned()? as u32; [[NOT PRESENT IN FullAOT]] } }); DECLARE_FIXED_LENGTH_CLUSTER!(Namespace, |_self, stream| { @@ -169,18 +177,26 @@ DECLARE_FIXED_LENGTH_CLUSTER!(UnlinkedCall, |_self, stream| { DECLARE_FIXED_LENGTH_CLUSTER!(ICData, |_self, stream| { for obj_idx in 0.._self.obj_count as usize { let obj = &mut *_self.objs[obj_idx]; + obj.target_name = stream.read_ref_id()?; + obj.args_descriptor = stream.read_ref_id()?; + obj.entries = stream.read_ref_id()?; obj.state_bits = stream.read_unsigned()? as u32; } }); DECLARE_FIXED_LENGTH_CLUSTER!(MegamorphicCache, |_self, stream| { for obj_idx in 0.._self.obj_count as usize { let obj = &mut *_self.objs[obj_idx]; + obj.target_name = stream.read_ref_id()?; + obj.args_descriptor = stream.read_ref_id()?; + obj.buckets = stream.read_ref_id()?; + obj.mask = stream.read_ref_id()? as i32; obj.filled_entry_count = stream.read()? as i32; } }); DECLARE_FIXED_LENGTH_CLUSTER!(SubtypeTestCache, |_self, stream| { for obj_idx in 0.._self.obj_count as usize { let obj = &mut *_self.objs[obj_idx]; + obj.cache = stream.read_ref_id()?; obj.num_inputs = stream.read_unsigned()? as u32; obj.num_occupied = stream.read_unsigned()? as u32; } @@ -200,8 +216,9 @@ DECLARE_FIXED_LENGTH_CLUSTER!(LanguageError, |_self, stream| { obj.script = stream.read_ref_id()?; obj.message = stream.read_ref_id()?; obj.formatted_message = stream.read_ref_id()?; + obj.token_pos = stream.read()? as i32; + obj.report_after_token = stream.read()? != 0; obj.kind = stream.read()? as i8; - obj.report_after_token = stream.read_unsigned()? != 0; } }); DECLARE_FIXED_LENGTH_CLUSTER!(UnhandledException, |_self, stream| { @@ -224,16 +241,22 @@ DECLARE_FIXED_LENGTH_CLUSTER!(LibraryPrefix, |_self, stream| { DECLARE_FIXED_LENGTH_CLUSTER!(Type, |_self, stream| { for obj_idx in 0.._self.obj_count as usize { let obj = &mut *_self.objs[obj_idx]; + obj.type_test_stub = stream.read_ref_id()?; + obj.hash = stream.read_ref_id()?; obj.arguments = stream.read_ref_id()?; + obj.flags = stream.read_unsigned()? as u8; } }); DECLARE_FIXED_LENGTH_CLUSTER!(FunctionType, |_self, stream| { for obj_idx in 0.._self.obj_count as usize { let obj = &mut *_self.objs[obj_idx]; + obj.type_test_stub = stream.read_ref_id()?; + obj.hash = stream.read_ref_id()?; obj.type_parameters = stream.read_ref_id()?; obj.result_type = stream.read_ref_id()?; obj.parameter_types = stream.read_ref_id()?; obj.named_parameter_names = stream.read_ref_id()?; + obj.flags = stream.read()? as u8; obj.packed_parameter_counts = stream.read_unsigned()? as u32; obj.packed_type_parameter_counts = stream.read_unsigned()? as u16; } @@ -241,16 +264,23 @@ DECLARE_FIXED_LENGTH_CLUSTER!(FunctionType, |_self, stream| { DECLARE_FIXED_LENGTH_CLUSTER!(RecordType, |_self, stream| { for obj_idx in 0.._self.obj_count as usize { let obj = &mut *_self.objs[obj_idx]; - obj.field_types = stream.read_ref_id()?; + obj.type_test_stub = stream.read_ref_id()?; + obj.hash = stream.read_ref_id()?; obj.shape = stream.read_ref_id()? as i32; + obj.field_types = stream.read_ref_id()?; + obj.flags = stream.read()? as u8; + // obj.shape = stream.read_ref_id()?; as i32; } }); DECLARE_FIXED_LENGTH_CLUSTER!(TypeParameter, |_self, stream| { for obj_idx in 0.._self.obj_count as usize { let obj = &mut *_self.objs[obj_idx]; + obj.type_test_stub = stream.read_ref_id()?; + obj.hash = stream.read_ref_id()?; obj.owner = stream.read_ref_id()?; - obj.base = stream.read_unsigned()? as u16; - obj.index = stream.read_unsigned()? as u16; + obj.base = stream.read()? as u16; + obj.index = stream.read()? as u16; + obj.flags = stream.read()? as u8; } }); DECLARE_FIXED_LENGTH_CLUSTER!(Closure, |_self, stream| { @@ -297,7 +327,7 @@ DECLARE_FIXED_LENGTH_CLUSTER!(StackTrace, |_self, stream| { obj.async_link = stream.read_ref_id()?; obj.code_array = stream.read_ref_id()?; obj.pc_offset_array = stream.read_ref_id()?; - obj.expand_inlined = stream.read_unsigned()? != 0; + // obj.expand_inlined = stream.read_unsigned()? != 0; [[NOT PRESENT IN FullAOT]] } }); DECLARE_FIXED_LENGTH_CLUSTER!(RegExp, |_self, stream| { @@ -309,6 +339,8 @@ DECLARE_FIXED_LENGTH_CLUSTER!(RegExp, |_self, stream| { obj.two_byte = stream.read_ref_id()?; obj.one_byte_sticky = stream.read_ref_id()?; obj.two_byte_sticky = stream.read_ref_id()?; + obj.num_one_byte_registers = stream.read()? as i32; + obj.num_two_byte_registers = stream.read()? as i32; obj.flags = stream.read_unsigned()? as u32; } }); @@ -317,7 +349,7 @@ DECLARE_FIXED_LENGTH_CLUSTER!(WeakProperty, |_self, stream| { let obj = &mut *_self.objs[obj_idx]; obj.key = stream.read_ref_id()?; obj.value = stream.read_ref_id()?; - obj.next_seen_by_gc = stream.read_ref_id()?; + // obj.next_seen_by_gc = stream.read_ref_id()?; [[NOT PRESENT IN FullAOT]] } }); diff --git a/crates/flutterdec-serwalker/src/raw_object/mod.rs b/crates/flutterdec-serwalker/src/raw_object/mod.rs index f44c873..cfea36a 100644 --- a/crates/flutterdec-serwalker/src/raw_object/mod.rs +++ b/crates/flutterdec-serwalker/src/raw_object/mod.rs @@ -53,12 +53,13 @@ pub struct Function { pub owner: u32, // ObjectPtr pub signature: u32, // FunctionTypePtr pub data: u32, // ObjectPtr - pub ic_data_array_or_bytecode: u32, // ObjectPtr - pub code: u32, // CodePtr - pub positional_parameter_names: u32, // ArrayPtr - pub unoptimized_code: u32, // CodePtr - pub bitmap: u64, - pub kernel_offset: u32, + // pub ic_data_array_or_bytecode: u32, // ObjectPtr [[NOT PRESENT IN FullAOT]] + pub code_index: u32, // unsigned integer index + // pub positional_parameter_names: u32, // ArrayPtr [[NOT PRESENT IN FullAOT]] + // pub unoptimized_code: u32, // CodePtr [[NOT PRESENT IN FullAOT]] + // pub bitmap: u64, [[NOT PRESENT IN FullAOT]] + pub token_pos: i32, + // pub kernel_offset: u32, [[NOT PRESENT IN FullAOT]] pub kind_tag: u32, } @@ -87,13 +88,17 @@ pub struct Field { pub type_field: u32, // AbstractTypePtr pub initializer_function: u32, // FunctionPtr pub host_offset_or_field_id: u32, // SmiPtr - pub guarded_list_length: u32, // SmiPtr - pub exact_type: u32, // AbstractTypePtr - pub dependent_code: u32, // WeakArrayPtr - pub kernel_offset: u32, - pub guarded_list_length_in_object_offset: i8, - pub static_type_exactness_state: i8, - pub target_offset: i32, + // pub guarded_list_length: u32, // SmiPtr [[NOT PRESENT IN FullAOT]] + // pub exact_type: u32, // AbstractTypePtr [[NOT PRESENT IN FullAOT]] + // pub dependent_code: u32, // WeakArrayPtr [[NOT PRESENT IN FullAOT]] + pub token_pos: i32, + pub end_token_pos: i32, + pub guarded_cid: u32, + pub is_nullable: u32, + // pub kernel_offset: u32, [[NOT PRESENT IN FullAOT]] + // pub guarded_list_length_in_object_offset: i8, [[NOT PRESENT IN FullAOT]] + // pub static_type_exactness_state: i8, [[NOT PRESENT IN FullAOT]] + // pub target_offset: i32, [[NOT PRESENT IN FullAOT]] pub kind_bits: u32, } @@ -114,13 +119,14 @@ pub struct Library { pub loading_unit: u32, // LoadingUnitPtr pub imports: u32, // ArrayPtr pub exports: u32, // ArrayPtr - pub dependencies: u32, // ArrayPtr - pub kernel_program_info: u32, // KernelProgramInfoPtr - pub loaded_scripts: u32, // ArrayPtr + // pub dependencies: u32, // ArrayPtr [[NOT PRESENT IN FullAOT]] + // pub kernel_program_info: u32, // KernelProgramInfoPtr [[NOT PRESENT IN FullAOT]] + // pub loaded_scripts: u32, // ArrayPtr [[NOT PRESENT IN FullAOT]] + pub index: i32, pub num_imports: u16, - pub flags: u8, - pub kernel_library_index: u32, pub load_state: i8, + pub flags: u8, + // pub kernel_library_index: u32, [[NOT PRESENT IN FullAOT]] } #[derive(Default)] @@ -212,14 +218,20 @@ pub struct TypeArguments { #[derive(Default)] pub struct TypeParameter { + pub type_test_stub: u32, // CodePtr + pub hash: u32, // SmiPtr pub owner: u32, // ObjectPtr pub base: u16, pub index: u16, + pub flags: u8, } #[derive(Default)] pub struct Type { + pub type_test_stub: u32, // CodePtr + pub hash: u32, // SmiPtr pub arguments: u32, // TypeArgumentsPtr + pub flags: u8, } #[derive(Default)] @@ -277,10 +289,13 @@ pub struct AbstractType { #[derive(Default)] pub struct FunctionType { + pub type_test_stub: u32, // CodePtr + pub hash: u32, // SmiPtr pub type_parameters: u32, // TypeParametersPtr pub result_type: u32, // AbstractTypePtr pub parameter_types: u32, // ArrayPtr pub named_parameter_names: u32, // ArrayPtr + pub flags: u8, pub packed_parameter_counts: u32, pub packed_type_parameter_counts: u16, } @@ -344,16 +359,24 @@ pub struct LoadingUnit { #[derive(Default)] pub struct ICData { + pub target_name: u32, + pub args_descriptor: u32, + pub entries: u32, pub state_bits: u32, } #[derive(Default)] pub struct MegamorphicCache { + pub target_name: u32, + pub args_descriptor: u32, + pub buckets: u32, + pub mask: Smi, pub filled_entry_count: i32, } #[derive(Default)] pub struct SubtypeTestCache { + pub cache: u32, pub num_inputs: u32, pub num_occupied: u32, } @@ -364,8 +387,9 @@ pub struct LanguageError { pub script: u32, // ScriptPtr pub message: u32, // StringPtr pub formatted_message: u32, // StringPtr - pub kind: i8, + pub token_pos: i32, pub report_after_token: bool, + pub kind: i8, } #[derive(Default)] @@ -385,8 +409,11 @@ pub struct LibraryPrefix { #[derive(Default)] pub struct RecordType { - pub field_types: u32, // ArrayPtr + pub type_test_stub: u32, // CodePtr + pub hash: u32, // SmiPtr pub shape: Smi, // SmiPtr + pub field_types: u32, // ArrayPtr + pub flags: u8, } #[derive(Default)] @@ -404,7 +431,7 @@ pub struct StackTrace { pub async_link: u32, // StackTracePtr pub code_array: u32, // ArrayPtr pub pc_offset_array: u32, // TypedDataPtr - pub expand_inlined: bool, + // pub expand_inlined: bool, [[NOT PRESENT IN FullAOT]] } #[derive(Default)] @@ -415,6 +442,8 @@ pub struct RegExp { pub two_byte: u32, // TypedDataPtr pub one_byte_sticky: u32, // TypedDataPtr pub two_byte_sticky: u32, // TypedDataPtr + pub num_one_byte_registers: i32, + pub num_two_byte_registers: i32, pub flags: u32, } @@ -422,7 +451,7 @@ pub struct RegExp { pub struct WeakProperty { pub key: u32, // ObjectPtr pub value: u32, // ObjectPtr - pub next_seen_by_gc: u32, // WeakPropertyPtr + // pub next_seen_by_gc: u32, // WeakPropertyPtr [[NOT PRESENT IN FullAOT]] } #[derive(Default)] diff --git a/crates/flutterdec-serwalker/src/snapshot.rs b/crates/flutterdec-serwalker/src/snapshot.rs index 8470a2e..bbc4d35 100644 --- a/crates/flutterdec-serwalker/src/snapshot.rs +++ b/crates/flutterdec-serwalker/src/snapshot.rs @@ -60,6 +60,8 @@ pub struct DataSnapshot { end_of_alloc_area: usize, end_of_fill_area: usize, + + objects: Vec } impl DataSnapshot { diff --git a/crates/flutterdec-serwalker/src/stream.rs b/crates/flutterdec-serwalker/src/stream.rs index a35a72a..330cf42 100644 --- a/crates/flutterdec-serwalker/src/stream.rs +++ b/crates/flutterdec-serwalker/src/stream.rs @@ -68,7 +68,7 @@ impl<'a> Stream<'a> { self.read_leb128(UNSIGNED_M) } - /// Raw fixed width little endian, header fields only. Everything inside the + /// Raw fixed width little endian, header fields only. Mostly everything else inside the /// clustered body is LEB128. Renamed so the two can't get mixed up. pub fn read_raw_u64(&mut self) -> Result { Ok(u64::from_le_bytes(self.take(8)?.try_into().expect("take(8) is 8 bytes"))) From d635a40de5a16d7bd2eb83990b91ba993e7bf84a Mon Sep 17 00:00:00 2001 From: kalixtez Date: Sun, 26 Jul 2026 22:31:28 -0500 Subject: [PATCH 35/50] fix(serwalker): cluster tags now uses the correct reading function, this is, read instead of read_unsigned --- .../flutterdec-serwalker/src/cluster/mod.rs | 18 ++---- crates/flutterdec-serwalker/src/lib.rs | 4 +- .../src/object_store/mod.rs | 1 + .../src/raw_object/mod.rs | 61 ++++++++++--------- crates/flutterdec-serwalker/src/snapshot.rs | 14 +++-- crates/flutterdec-serwalker/src/stream.rs | 40 +++++++++--- crates/flutterdec-serwalker/src/utils.rs | 5 +- 7 files changed, 84 insertions(+), 59 deletions(-) diff --git a/crates/flutterdec-serwalker/src/cluster/mod.rs b/crates/flutterdec-serwalker/src/cluster/mod.rs index 9dbb92f..e0e8f64 100644 --- a/crates/flutterdec-serwalker/src/cluster/mod.rs +++ b/crates/flutterdec-serwalker/src/cluster/mod.rs @@ -376,8 +376,7 @@ DECLARE_VARIABLE_LENGTH_CLUSTER!(ConstSet); DECLARE_VARIABLE_LENGTH_CLUSTER!(CodeSourceMap); DECLARE_VARIABLE_LENGTH_CLUSTER!(CompressedStackMaps); -impl Cluster for CompressedStackMapsCluster -{ +impl Cluster for CompressedStackMapsCluster { fn is_fixed_len(&self) -> bool { false } @@ -387,8 +386,7 @@ impl Cluster for CompressedStackMapsCluster self.first_ref_id = *last_ref_id as u32; self.obj_count = stream.read_unsigned()?; - for _obj_idx in 0..self.obj_count - { + for _obj_idx in 0..self.obj_count { let mut obj = Box::::default(); obj.length = stream.read_unsigned()? as u32; @@ -403,8 +401,7 @@ impl Cluster for CompressedStackMapsCluster fn read_fill(&mut self, stream: &mut Stream) -> anyhow::Result { self.start_of_fill = stream.get_current_pos(); - for obj_idx in 0..self.obj_count - { + for obj_idx in 0..self.obj_count { let obj = &mut self.objs[obj_idx as usize]; obj.flags_and_size = stream.read_unsigned()? as u32; @@ -419,8 +416,7 @@ impl Cluster for CompressedStackMapsCluster DECLARE_VARIABLE_LENGTH_CLUSTER!(PcDescriptors); -impl Cluster for PcDescriptorsCluster -{ +impl Cluster for PcDescriptorsCluster { fn is_fixed_len(&self) -> bool { false } @@ -431,8 +427,7 @@ impl Cluster for PcDescriptorsCluster self.obj_count = stream.read_unsigned()?; - for _obj_idx in 0..self.obj_count - { + for _obj_idx in 0..self.obj_count { let mut obj = Box::::default(); let length = stream.read_unsigned()?; @@ -449,8 +444,7 @@ impl Cluster for PcDescriptorsCluster fn read_fill(&mut self, stream: &mut Stream) -> anyhow::Result { self.start_of_fill = stream.get_current_pos(); - for obj_idx in 0..self.obj_count - { + for obj_idx in 0..self.obj_count { let length = stream.read_unsigned()?; // its saved twice let variable_size_data = stream.read_bytes(length as usize)?; diff --git a/crates/flutterdec-serwalker/src/lib.rs b/crates/flutterdec-serwalker/src/lib.rs index 29bcf02..1197ae1 100644 --- a/crates/flutterdec-serwalker/src/lib.rs +++ b/crates/flutterdec-serwalker/src/lib.rs @@ -2,8 +2,8 @@ mod constants; mod utils; mod cluster; +mod object_store; mod raw_object; mod snapshot; -mod object_store; -mod stream; \ No newline at end of file +mod stream; diff --git a/crates/flutterdec-serwalker/src/object_store/mod.rs b/crates/flutterdec-serwalker/src/object_store/mod.rs index e69de29..8b13789 100644 --- a/crates/flutterdec-serwalker/src/object_store/mod.rs +++ b/crates/flutterdec-serwalker/src/object_store/mod.rs @@ -0,0 +1 @@ + diff --git a/crates/flutterdec-serwalker/src/raw_object/mod.rs b/crates/flutterdec-serwalker/src/raw_object/mod.rs index cfea36a..7afb310 100644 --- a/crates/flutterdec-serwalker/src/raw_object/mod.rs +++ b/crates/flutterdec-serwalker/src/raw_object/mod.rs @@ -49,12 +49,12 @@ pub struct PatchClass { #[derive(Default)] pub struct Function { - pub name: u32, // StringPtr - pub owner: u32, // ObjectPtr - pub signature: u32, // FunctionTypePtr - pub data: u32, // ObjectPtr + pub name: u32, // StringPtr + pub owner: u32, // ObjectPtr + pub signature: u32, // FunctionTypePtr + pub data: u32, // ObjectPtr // pub ic_data_array_or_bytecode: u32, // ObjectPtr [[NOT PRESENT IN FullAOT]] - pub code_index: u32, // unsigned integer index + pub code_index: u32, // unsigned integer index // pub positional_parameter_names: u32, // ArrayPtr [[NOT PRESENT IN FullAOT]] // pub unoptimized_code: u32, // CodePtr [[NOT PRESENT IN FullAOT]] // pub bitmap: u64, [[NOT PRESENT IN FullAOT]] @@ -109,16 +109,16 @@ pub struct Script { #[derive(Default)] pub struct Library { - pub name: u32, // StringPtr - pub url: u32, // StringPtr - pub private_key: u32, // StringPtr - pub dictionary: u32, // ArrayPtr - pub metadata: u32, // ArrayPtr - pub toplevel_class: u32, // ClassPtr - pub used_scripts: u32, // GrowableObjectArrayPtr - pub loading_unit: u32, // LoadingUnitPtr - pub imports: u32, // ArrayPtr - pub exports: u32, // ArrayPtr + pub name: u32, // StringPtr + pub url: u32, // StringPtr + pub private_key: u32, // StringPtr + pub dictionary: u32, // ArrayPtr + pub metadata: u32, // ArrayPtr + pub toplevel_class: u32, // ClassPtr + pub used_scripts: u32, // GrowableObjectArrayPtr + pub loading_unit: u32, // LoadingUnitPtr + pub imports: u32, // ArrayPtr + pub exports: u32, // ArrayPtr // pub dependencies: u32, // ArrayPtr [[NOT PRESENT IN FullAOT]] // pub kernel_program_info: u32, // KernelProgramInfoPtr [[NOT PRESENT IN FullAOT]] // pub loaded_scripts: u32, // ArrayPtr [[NOT PRESENT IN FullAOT]] @@ -161,13 +161,14 @@ pub struct CodeSourceMap { pub struct CompressedStackMaps { pub length: u32, pub flags_and_size: u32, - pub data: Vec + pub data: Vec, } #[derive(Default)] -pub struct PcDescriptors { // not really +pub struct PcDescriptors { + // not really pub length: u32, - pub data: Vec + pub data: Vec, } #[derive(Default)] @@ -220,7 +221,7 @@ pub struct TypeArguments { pub struct TypeParameter { pub type_test_stub: u32, // CodePtr pub hash: u32, // SmiPtr - pub owner: u32, // ObjectPtr + pub owner: u32, // ObjectPtr pub base: u16, pub index: u16, pub flags: u8, @@ -230,7 +231,7 @@ pub struct TypeParameter { pub struct Type { pub type_test_stub: u32, // CodePtr pub hash: u32, // SmiPtr - pub arguments: u32, // TypeArgumentsPtr + pub arguments: u32, // TypeArgumentsPtr pub flags: u8, } @@ -289,8 +290,8 @@ pub struct AbstractType { #[derive(Default)] pub struct FunctionType { - pub type_test_stub: u32, // CodePtr - pub hash: u32, // SmiPtr + pub type_test_stub: u32, // CodePtr + pub hash: u32, // SmiPtr pub type_parameters: u32, // TypeParametersPtr pub result_type: u32, // AbstractTypePtr pub parameter_types: u32, // ArrayPtr @@ -411,8 +412,8 @@ pub struct LibraryPrefix { pub struct RecordType { pub type_test_stub: u32, // CodePtr pub hash: u32, // SmiPtr - pub shape: Smi, // SmiPtr - pub field_types: u32, // ArrayPtr + pub shape: Smi, // SmiPtr + pub field_types: u32, // ArrayPtr pub flags: u8, } @@ -428,10 +429,10 @@ pub struct ExternalTypedData { #[derive(Default)] pub struct StackTrace { - pub async_link: u32, // StackTracePtr - pub code_array: u32, // ArrayPtr + pub async_link: u32, // StackTracePtr + pub code_array: u32, // ArrayPtr pub pc_offset_array: u32, // TypedDataPtr - // pub expand_inlined: bool, [[NOT PRESENT IN FullAOT]] + // pub expand_inlined: bool, [[NOT PRESENT IN FullAOT]] } #[derive(Default)] @@ -449,9 +450,9 @@ pub struct RegExp { #[derive(Default)] pub struct WeakProperty { - pub key: u32, // ObjectPtr - pub value: u32, // ObjectPtr - // pub next_seen_by_gc: u32, // WeakPropertyPtr [[NOT PRESENT IN FullAOT]] + pub key: u32, // ObjectPtr + pub value: u32, // ObjectPtr + // pub next_seen_by_gc: u32, // WeakPropertyPtr [[NOT PRESENT IN FullAOT]] } #[derive(Default)] diff --git a/crates/flutterdec-serwalker/src/snapshot.rs b/crates/flutterdec-serwalker/src/snapshot.rs index 87e7a83..adf8b9e 100644 --- a/crates/flutterdec-serwalker/src/snapshot.rs +++ b/crates/flutterdec-serwalker/src/snapshot.rs @@ -59,7 +59,7 @@ pub struct DataSnapshot { end_of_alloc_area: usize, end_of_fill_area: usize, - objects: Vec + objects: Vec, } impl DataSnapshot { @@ -79,7 +79,8 @@ impl DataSnapshot { } self.size = stream.read_raw_u64()?; - self.kind = SnapshotKind::try_from(stream.read_raw_u64()?).map_err(|e| anyhow::anyhow!(e))?; + self.kind = + SnapshotKind::try_from(stream.read_raw_u64()?).map_err(|e| anyhow::anyhow!(e))?; self.parse_version_and_features(stream)?; @@ -97,7 +98,7 @@ impl DataSnapshot { self.start_of_alloc_area = stream.get_current_pos(); for _cluster_idx in 0..self.num_clusters { - let tags: u32 = stream.read_unsigned()? as u32; + let tags: u32 = stream.read()? as u32; let decoded_tags: DecodedTags = decode_tags(tags)?; let cid = decoded_tags.get_cid(); @@ -106,9 +107,11 @@ impl DataSnapshot { })?; cluster.read_alloc(&mut curr_ref_id, stream)?; - + // Composite key exactly as suggested by PR reviewer - let key = (cid as u32) << 2 | ((decoded_tags.is_canonical() as u32) << 1) | (decoded_tags.is_deeply_immutable() as u32); + let key = (cid as u32) << 2 + | ((decoded_tags.is_canonical() as u32) << 1) + | (decoded_tags.is_deeply_immutable() as u32); self.clusters.insert(key, cluster); self.cluster_order.push(key); } @@ -138,4 +141,3 @@ pub fn parse_snapshot(stream: &mut Stream) -> anyhow::Result { Ok(snapshot) } - diff --git a/crates/flutterdec-serwalker/src/stream.rs b/crates/flutterdec-serwalker/src/stream.rs index 330cf42..8d4893c 100644 --- a/crates/flutterdec-serwalker/src/stream.rs +++ b/crates/flutterdec-serwalker/src/stream.rs @@ -9,12 +9,18 @@ pub struct Stream<'a> { impl<'a> Stream<'a> { pub fn new(byte_stream: &'a [u8]) -> Self { - Self { byte_stream, curr_stream_offset: 0 } + Self { + byte_stream, + curr_stream_offset: 0, + } } pub fn seek(&mut self, pos: usize) -> Result<()> { if pos > self.byte_stream.len() { - bail!("seek to {pos} past end of snapshot ({})", self.byte_stream.len()); + bail!( + "seek to {pos} past end of snapshot ({})", + self.byte_stream.len() + ); } self.curr_stream_offset = pos; Ok(()) @@ -26,7 +32,10 @@ impl<'a> Stream<'a> { .checked_add(n) .filter(|e| *e <= self.byte_stream.len()) .ok_or_else(|| { - anyhow!("read of {n} bytes past end of snapshot at offset {}", self.curr_stream_offset) + anyhow!( + "read of {n} bytes past end of snapshot at offset {}", + self.curr_stream_offset + ) })?; let slice = &self.byte_stream[self.curr_stream_offset..end]; self.curr_stream_offset = end; @@ -53,7 +62,10 @@ impl<'a> Stream<'a> { value |= (byte as u64) << shift; shift += DATA_BITS_PER_BYTE; if shift >= 64 { - bail!("LEB128 value exceeds 64 bits at offset {}", self.curr_stream_offset); + bail!( + "LEB128 value exceeds 64 bits at offset {}", + self.curr_stream_offset + ); } } } @@ -71,11 +83,15 @@ impl<'a> Stream<'a> { /// Raw fixed width little endian, header fields only. Mostly everything else inside the /// clustered body is LEB128. Renamed so the two can't get mixed up. pub fn read_raw_u64(&mut self) -> Result { - Ok(u64::from_le_bytes(self.take(8)?.try_into().expect("take(8) is 8 bytes"))) + Ok(u64::from_le_bytes( + self.take(8)?.try_into().expect("take(8) is 8 bytes"), + )) } pub fn read_raw_u32(&mut self) -> Result { - Ok(u32::from_le_bytes(self.take(4)?.try_into().expect("take(4) is 4 bytes"))) + Ok(u32::from_le_bytes( + self.take(4)?.try_into().expect("take(4) is 4 bytes"), + )) } pub fn read_byte(&mut self) -> Result { @@ -86,7 +102,12 @@ impl<'a> Stream<'a> { let nul = self.byte_stream[self.curr_stream_offset..] .iter() .position(|&b| b == 0) - .ok_or_else(|| anyhow!("unterminated C string at offset {}", self.curr_stream_offset))?; + .ok_or_else(|| { + anyhow!( + "unterminated C string at offset {}", + self.curr_stream_offset + ) + })?; let raw = self.take(nul + 1)?; Ok(String::from_utf8(raw[..nul].to_vec())?) } @@ -107,7 +128,10 @@ impl<'a> Stream<'a> { return Ok((result + 128) as u32); } } - bail!("ref id longer than 5 bytes at offset {}", self.curr_stream_offset) + bail!( + "ref id longer than 5 bytes at offset {}", + self.curr_stream_offset + ) } /// Reads a block of bytes and returns a newly allocated Vec (Creates a copy) diff --git a/crates/flutterdec-serwalker/src/utils.rs b/crates/flutterdec-serwalker/src/utils.rs index 0616db0..1445c27 100644 --- a/crates/flutterdec-serwalker/src/utils.rs +++ b/crates/flutterdec-serwalker/src/utils.rs @@ -137,7 +137,10 @@ macro_rules! DECODE_IS_CANONICAL { /// ClassIdTag at 12 (20 bits), CanonicalBit at 1, ImmutableBit at 7. pub fn decode_tags(tags: u32) -> anyhow::Result { let class_id = DECODE_CID!(tags).map_err(|_| { - anyhow::anyhow!("unknown class id {} in tags {tags:#x}", (tags >> 12) & 0xFFFFF) + anyhow::anyhow!( + "unknown class id {} in tags {tags:#x}", + (tags >> 12) & 0xFFFFF + ) })?; Ok(DecodedTags::new( class_id, From e83da1e000d350f56a31a719b7ece14a31205c45 Mon Sep 17 00:00:00 2001 From: kalixtez Date: Mon, 27 Jul 2026 01:42:58 -0500 Subject: [PATCH 36/50] serwalker(refactor): refactored macro calls after the removal of the paste dependency --- Cargo.lock | 7 -- crates/flutterdec-serwalker/Cargo.toml | 1 - .../flutterdec-serwalker/src/cluster/mod.rs | 114 +++++++++--------- crates/flutterdec-serwalker/src/snapshot.rs | 2 - crates/flutterdec-serwalker/src/utils.rs | 42 +++---- 5 files changed, 76 insertions(+), 90 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a49a408..77fa0c4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -367,7 +367,6 @@ version = "0.1.0-alpha.2" dependencies = [ "anyhow", "goblin", - "paste", ] [[package]] @@ -573,12 +572,6 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" -[[package]] -name = "paste" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" - [[package]] name = "plain" version = "0.2.3" diff --git a/crates/flutterdec-serwalker/Cargo.toml b/crates/flutterdec-serwalker/Cargo.toml index b82e49c..281dfc9 100644 --- a/crates/flutterdec-serwalker/Cargo.toml +++ b/crates/flutterdec-serwalker/Cargo.toml @@ -7,4 +7,3 @@ license.workspace = true [dependencies] anyhow.workspace = true goblin.workspace = true -paste = "1.0.15" diff --git a/crates/flutterdec-serwalker/src/cluster/mod.rs b/crates/flutterdec-serwalker/src/cluster/mod.rs index e0e8f64..3979f92 100644 --- a/crates/flutterdec-serwalker/src/cluster/mod.rs +++ b/crates/flutterdec-serwalker/src/cluster/mod.rs @@ -39,7 +39,7 @@ pub fn decide_cluster(class_id: ClassId) -> Result, &'static st // whose fill cluster size is uniquely determined by sizeof(Object) * num_of_objects // and alloc cluster size is tags (MULEB128) + num_of_objects (MULEB128) -DECLARE_FIXED_LENGTH_CLUSTER!(TypeParameters, |_self, stream| { +DECLARE_FIXED_LENGTH_CLUSTER!(TypeParameters, TypeParametersCluster, |_self, stream| { for obj_idx in 0.._self.obj_count as usize { let obj = &mut *_self.objs[obj_idx]; obj.names = stream.read_ref_id()?; @@ -48,7 +48,7 @@ DECLARE_FIXED_LENGTH_CLUSTER!(TypeParameters, |_self, stream| { obj.defaults = stream.read_ref_id()?; } }); -DECLARE_FIXED_LENGTH_CLUSTER!(PatchClass, |_self, stream| { +DECLARE_FIXED_LENGTH_CLUSTER!(PatchClass, PatchClassCluster, |_self, stream| { for obj_idx in 0.._self.obj_count as usize { let obj = &mut *_self.objs[obj_idx]; obj.wrapped_class = stream.read_ref_id()?; @@ -57,7 +57,7 @@ DECLARE_FIXED_LENGTH_CLUSTER!(PatchClass, |_self, stream| { // obj.kernel_library_index = stream.read_unsigned()? as u32; [[NOT PRESENT IN FullAOT]] } }); -DECLARE_FIXED_LENGTH_CLUSTER!(Function, |_self, stream| { +DECLARE_FIXED_LENGTH_CLUSTER!(Function, FunctionCluster, |_self, stream| { for obj_idx in 0.._self.obj_count as usize { let obj = &mut *_self.objs[obj_idx]; obj.name = stream.read_ref_id()?; @@ -75,7 +75,7 @@ DECLARE_FIXED_LENGTH_CLUSTER!(Function, |_self, stream| { obj.kind_tag = stream.read_unsigned()? as u32; } }); -DECLARE_FIXED_LENGTH_CLUSTER!(ClosureData, |_self, stream| { +DECLARE_FIXED_LENGTH_CLUSTER!(ClosureData, ClosureDataCluster, |_self, stream| { for obj_idx in 0.._self.obj_count as usize { let obj = &mut *_self.objs[obj_idx]; obj.context_scope = stream.read_ref_id()?; @@ -84,7 +84,7 @@ DECLARE_FIXED_LENGTH_CLUSTER!(ClosureData, |_self, stream| { obj.packed_fields = stream.read_unsigned()? as u32; } }); -DECLARE_FIXED_LENGTH_CLUSTER!(FfiTrampolineData, |_self, stream| { +DECLARE_FIXED_LENGTH_CLUSTER!(FfiTrampolineData, FfiTrampolineDataCluster, |_self, stream| { for obj_idx in 0.._self.obj_count as usize { let obj = &mut *_self.objs[obj_idx]; obj.signature_type = stream.read_ref_id()?; @@ -95,7 +95,7 @@ DECLARE_FIXED_LENGTH_CLUSTER!(FfiTrampolineData, |_self, stream| { obj.callback_id = stream.read()? as i32; } }); -DECLARE_FIXED_LENGTH_CLUSTER!(Field, |_self, stream| { +DECLARE_FIXED_LENGTH_CLUSTER!(Field, FieldCluster, |_self, stream| { for obj_idx in 0.._self.obj_count as usize { let obj = &mut *_self.objs[obj_idx]; obj.name = stream.read_ref_id()?; @@ -117,10 +117,10 @@ DECLARE_FIXED_LENGTH_CLUSTER!(Field, |_self, stream| { obj.kind_bits = stream.read_unsigned()? as u32; } }); -DECLARE_FIXED_LENGTH_CLUSTER!(Script, |_self, stream| { +DECLARE_FIXED_LENGTH_CLUSTER!(Script, ScriptCluster, |_self, stream| { for _ in 0.._self.obj_count as usize {} }); -DECLARE_FIXED_LENGTH_CLUSTER!(Library, |_self, stream| { +DECLARE_FIXED_LENGTH_CLUSTER!(Library, LibraryCluster, |_self, stream| { for obj_idx in 0.._self.obj_count as usize { let obj = &mut *_self.objs[obj_idx]; obj.name = stream.read_ref_id()?; @@ -143,7 +143,7 @@ DECLARE_FIXED_LENGTH_CLUSTER!(Library, |_self, stream| { // obj.kernel_library_index = stream.read_unsigned()? as u32; [[NOT PRESENT IN FullAOT]] } }); -DECLARE_FIXED_LENGTH_CLUSTER!(Namespace, |_self, stream| { +DECLARE_FIXED_LENGTH_CLUSTER!(Namespace, NamespaceCluster, |_self, stream| { for obj_idx in 0.._self.obj_count as usize { let obj = &mut *_self.objs[obj_idx]; obj.target = stream.read_ref_id()?; @@ -152,7 +152,7 @@ DECLARE_FIXED_LENGTH_CLUSTER!(Namespace, |_self, stream| { obj.owner = stream.read_ref_id()?; } }); -DECLARE_FIXED_LENGTH_CLUSTER!(KernelProgramInfo, |_self, stream| { +DECLARE_FIXED_LENGTH_CLUSTER!(KernelProgramInfo, KernelProgramInfoCluster, |_self, stream| { for obj_idx in 0.._self.obj_count as usize { let obj = &mut *_self.objs[obj_idx]; obj.kernel_component = stream.read_ref_id()?; @@ -168,13 +168,13 @@ DECLARE_FIXED_LENGTH_CLUSTER!(KernelProgramInfo, |_self, stream| { obj.classes_cache = stream.read_ref_id()?; } }); -DECLARE_FIXED_LENGTH_CLUSTER!(UnlinkedCall, |_self, stream| { +DECLARE_FIXED_LENGTH_CLUSTER!(UnlinkedCall, UnlinkedCallCluster, |_self, stream| { for obj_idx in 0.._self.obj_count as usize { let obj = &mut *_self.objs[obj_idx]; obj.can_patch_to_monomorphic = stream.read_unsigned()? != 0; } }); -DECLARE_FIXED_LENGTH_CLUSTER!(ICData, |_self, stream| { +DECLARE_FIXED_LENGTH_CLUSTER!(ICData, ICDataCluster, |_self, stream| { for obj_idx in 0.._self.obj_count as usize { let obj = &mut *_self.objs[obj_idx]; obj.target_name = stream.read_ref_id()?; @@ -183,7 +183,7 @@ DECLARE_FIXED_LENGTH_CLUSTER!(ICData, |_self, stream| { obj.state_bits = stream.read_unsigned()? as u32; } }); -DECLARE_FIXED_LENGTH_CLUSTER!(MegamorphicCache, |_self, stream| { +DECLARE_FIXED_LENGTH_CLUSTER!(MegamorphicCache, MegamorphicCacheCluster, |_self, stream| { for obj_idx in 0.._self.obj_count as usize { let obj = &mut *_self.objs[obj_idx]; obj.target_name = stream.read_ref_id()?; @@ -193,7 +193,7 @@ DECLARE_FIXED_LENGTH_CLUSTER!(MegamorphicCache, |_self, stream| { obj.filled_entry_count = stream.read()? as i32; } }); -DECLARE_FIXED_LENGTH_CLUSTER!(SubtypeTestCache, |_self, stream| { +DECLARE_FIXED_LENGTH_CLUSTER!(SubtypeTestCache, SubtypeTestCacheCluster, |_self, stream| { for obj_idx in 0.._self.obj_count as usize { let obj = &mut *_self.objs[obj_idx]; obj.cache = stream.read_ref_id()?; @@ -201,7 +201,7 @@ DECLARE_FIXED_LENGTH_CLUSTER!(SubtypeTestCache, |_self, stream| { obj.num_occupied = stream.read_unsigned()? as u32; } }); -DECLARE_FIXED_LENGTH_CLUSTER!(LoadingUnit, |_self, stream| { +DECLARE_FIXED_LENGTH_CLUSTER!(LoadingUnit, LoadingUnitCluster, |_self, stream| { for obj_idx in 0.._self.obj_count as usize { let obj = &mut *_self.objs[obj_idx]; obj.parent = stream.read_ref_id()?; @@ -209,7 +209,7 @@ DECLARE_FIXED_LENGTH_CLUSTER!(LoadingUnit, |_self, stream| { obj.packed_fields = stream.read()? as i64; } }); -DECLARE_FIXED_LENGTH_CLUSTER!(LanguageError, |_self, stream| { +DECLARE_FIXED_LENGTH_CLUSTER!(LanguageError, LanguageErrorCluster, |_self, stream| { for obj_idx in 0.._self.obj_count as usize { let obj = &mut *_self.objs[obj_idx]; obj.previous_error = stream.read_ref_id()?; @@ -221,14 +221,14 @@ DECLARE_FIXED_LENGTH_CLUSTER!(LanguageError, |_self, stream| { obj.kind = stream.read()? as i8; } }); -DECLARE_FIXED_LENGTH_CLUSTER!(UnhandledException, |_self, stream| { +DECLARE_FIXED_LENGTH_CLUSTER!(UnhandledException, UnhandledExceptionCluster, |_self, stream| { for obj_idx in 0.._self.obj_count as usize { let obj = &mut *_self.objs[obj_idx]; obj.exception = stream.read_ref_id()?; obj.stacktrace = stream.read_ref_id()?; } }); -DECLARE_FIXED_LENGTH_CLUSTER!(LibraryPrefix, |_self, stream| { +DECLARE_FIXED_LENGTH_CLUSTER!(LibraryPrefix, LibraryPrefixCluster, |_self, stream| { for obj_idx in 0.._self.obj_count as usize { let obj = &mut *_self.objs[obj_idx]; obj.name = stream.read_ref_id()?; @@ -238,7 +238,7 @@ DECLARE_FIXED_LENGTH_CLUSTER!(LibraryPrefix, |_self, stream| { obj.is_deferred_load = stream.read_unsigned()? != 0; } }); -DECLARE_FIXED_LENGTH_CLUSTER!(Type, |_self, stream| { +DECLARE_FIXED_LENGTH_CLUSTER!(Type, TypeCluster, |_self, stream| { for obj_idx in 0.._self.obj_count as usize { let obj = &mut *_self.objs[obj_idx]; obj.type_test_stub = stream.read_ref_id()?; @@ -247,7 +247,7 @@ DECLARE_FIXED_LENGTH_CLUSTER!(Type, |_self, stream| { obj.flags = stream.read_unsigned()? as u8; } }); -DECLARE_FIXED_LENGTH_CLUSTER!(FunctionType, |_self, stream| { +DECLARE_FIXED_LENGTH_CLUSTER!(FunctionType, FunctionTypeCluster, |_self, stream| { for obj_idx in 0.._self.obj_count as usize { let obj = &mut *_self.objs[obj_idx]; obj.type_test_stub = stream.read_ref_id()?; @@ -261,7 +261,7 @@ DECLARE_FIXED_LENGTH_CLUSTER!(FunctionType, |_self, stream| { obj.packed_type_parameter_counts = stream.read_unsigned()? as u16; } }); -DECLARE_FIXED_LENGTH_CLUSTER!(RecordType, |_self, stream| { +DECLARE_FIXED_LENGTH_CLUSTER!(RecordType, RecordTypeCluster, |_self, stream| { for obj_idx in 0.._self.obj_count as usize { let obj = &mut *_self.objs[obj_idx]; obj.type_test_stub = stream.read_ref_id()?; @@ -272,7 +272,7 @@ DECLARE_FIXED_LENGTH_CLUSTER!(RecordType, |_self, stream| { // obj.shape = stream.read_ref_id()?; as i32; } }); -DECLARE_FIXED_LENGTH_CLUSTER!(TypeParameter, |_self, stream| { +DECLARE_FIXED_LENGTH_CLUSTER!(TypeParameter, TypeParameterCluster, |_self, stream| { for obj_idx in 0.._self.obj_count as usize { let obj = &mut *_self.objs[obj_idx]; obj.type_test_stub = stream.read_ref_id()?; @@ -283,7 +283,7 @@ DECLARE_FIXED_LENGTH_CLUSTER!(TypeParameter, |_self, stream| { obj.flags = stream.read()? as u8; } }); -DECLARE_FIXED_LENGTH_CLUSTER!(Closure, |_self, stream| { +DECLARE_FIXED_LENGTH_CLUSTER!(Closure, ClosureCluster, |_self, stream| { for obj_idx in 0.._self.obj_count as usize { let obj = &mut *_self.objs[obj_idx]; obj.instantiator_type_arguments = stream.read_ref_id()?; @@ -294,16 +294,16 @@ DECLARE_FIXED_LENGTH_CLUSTER!(Closure, |_self, stream| { obj.hash = stream.read_ref_id()?; } }); -DECLARE_FIXED_LENGTH_CLUSTER!(Double, |_self, stream| { +DECLARE_FIXED_LENGTH_CLUSTER!(Double, DoubleCluster, |_self, stream| { for obj_idx in 0.._self.obj_count as usize { let obj = &mut *_self.objs[obj_idx]; obj.value = f64::from_bits(stream.read_raw_u64()?); } }); -DECLARE_FIXED_LENGTH_CLUSTER!(Int32x4, |_self, stream| { +DECLARE_FIXED_LENGTH_CLUSTER!(Int32x4, Int32x4Cluster, |_self, stream| { for _ in 0.._self.obj_count as usize {} }); -DECLARE_FIXED_LENGTH_CLUSTER!(GrowableObjectArray, |_self, stream| { +DECLARE_FIXED_LENGTH_CLUSTER!(GrowableObjectArray, GrowableObjectArrayCluster, |_self, stream| { for obj_idx in 0.._self.obj_count as usize { let obj = &mut *_self.objs[obj_idx]; obj.type_arguments = stream.read_ref_id()?; @@ -311,17 +311,17 @@ DECLARE_FIXED_LENGTH_CLUSTER!(GrowableObjectArray, |_self, stream| { obj.length = stream.read_ref_id()? as i32; } }); -DECLARE_FIXED_LENGTH_CLUSTER!(TypedDataView, |_self, stream| { +DECLARE_FIXED_LENGTH_CLUSTER!(TypedDataView, TypedDataViewCluster, |_self, stream| { for obj_idx in 0.._self.obj_count as usize { let obj = &mut *_self.objs[obj_idx]; obj.typed_data = stream.read_ref_id()?; obj.offset_in_bytes = stream.read_ref_id()? as i32; } }); -DECLARE_FIXED_LENGTH_CLUSTER!(ExternalTypedData, |_self, stream| { +DECLARE_FIXED_LENGTH_CLUSTER!(ExternalTypedData, ExternalTypedDataCluster, |_self, stream| { for _ in 0.._self.obj_count as usize {} }); -DECLARE_FIXED_LENGTH_CLUSTER!(StackTrace, |_self, stream| { +DECLARE_FIXED_LENGTH_CLUSTER!(StackTrace, StackTraceCluster, |_self, stream| { for obj_idx in 0.._self.obj_count as usize { let obj = &mut *_self.objs[obj_idx]; obj.async_link = stream.read_ref_id()?; @@ -330,7 +330,7 @@ DECLARE_FIXED_LENGTH_CLUSTER!(StackTrace, |_self, stream| { // obj.expand_inlined = stream.read_unsigned()? != 0; [[NOT PRESENT IN FullAOT]] } }); -DECLARE_FIXED_LENGTH_CLUSTER!(RegExp, |_self, stream| { +DECLARE_FIXED_LENGTH_CLUSTER!(RegExp, RegExpCluster, |_self, stream| { for obj_idx in 0.._self.obj_count as usize { let obj = &mut *_self.objs[obj_idx]; obj.capture_name_map = stream.read_ref_id()?; @@ -344,7 +344,7 @@ DECLARE_FIXED_LENGTH_CLUSTER!(RegExp, |_self, stream| { obj.flags = stream.read_unsigned()? as u32; } }); -DECLARE_FIXED_LENGTH_CLUSTER!(WeakProperty, |_self, stream| { +DECLARE_FIXED_LENGTH_CLUSTER!(WeakProperty, WeakPropertyCluster, |_self, stream| { for obj_idx in 0.._self.obj_count as usize { let obj = &mut *_self.objs[obj_idx]; obj.key = stream.read_ref_id()?; @@ -353,28 +353,28 @@ DECLARE_FIXED_LENGTH_CLUSTER!(WeakProperty, |_self, stream| { } }); -DECLARE_VARIABLE_LENGTH_CLUSTER!(Map); -DECLARE_VARIABLE_LENGTH_CLUSTER!(Set); -DECLARE_VARIABLE_LENGTH_CLUSTER!(Instance); -DECLARE_VARIABLE_LENGTH_CLUSTER!(TypedData); -DECLARE_VARIABLE_LENGTH_CLUSTER!(Class); -DECLARE_VARIABLE_LENGTH_CLUSTER!(TypeArguments); -DECLARE_VARIABLE_LENGTH_CLUSTER!(Code); -DECLARE_VARIABLE_LENGTH_CLUSTER!(ObjectPool); -DECLARE_VARIABLE_LENGTH_CLUSTER!(ExceptionHandlers); -DECLARE_VARIABLE_LENGTH_CLUSTER!(Context); -DECLARE_VARIABLE_LENGTH_CLUSTER!(ContextScope); -DECLARE_VARIABLE_LENGTH_CLUSTER!(Mint); -DECLARE_VARIABLE_LENGTH_CLUSTER!(Float32x4); -DECLARE_VARIABLE_LENGTH_CLUSTER!(Float64x2); -DECLARE_VARIABLE_LENGTH_CLUSTER!(Record); -DECLARE_VARIABLE_LENGTH_CLUSTER!(Array); -DECLARE_VARIABLE_LENGTH_CLUSTER!(WeakArray); -DECLARE_VARIABLE_LENGTH_CLUSTER!(ImmutableArray); -DECLARE_VARIABLE_LENGTH_CLUSTER!(ConstMap); -DECLARE_VARIABLE_LENGTH_CLUSTER!(ConstSet); -DECLARE_VARIABLE_LENGTH_CLUSTER!(CodeSourceMap); -DECLARE_VARIABLE_LENGTH_CLUSTER!(CompressedStackMaps); +DECLARE_VARIABLE_LENGTH_CLUSTER!(Map, MapCluster); +DECLARE_VARIABLE_LENGTH_CLUSTER!(Set, SetCluster); +DECLARE_VARIABLE_LENGTH_CLUSTER!(Instance, InstanceCluster); +DECLARE_VARIABLE_LENGTH_CLUSTER!(TypedData, TypedDataCluster); +DECLARE_VARIABLE_LENGTH_CLUSTER!(Class, ClassCluster); +DECLARE_VARIABLE_LENGTH_CLUSTER!(TypeArguments, TypeArgumentsCluster); +DECLARE_VARIABLE_LENGTH_CLUSTER!(Code, CodeCluster); +DECLARE_VARIABLE_LENGTH_CLUSTER!(ObjectPool, ObjectPoolCluster); +DECLARE_VARIABLE_LENGTH_CLUSTER!(ExceptionHandlers, ExceptionHandlersCluster); +DECLARE_VARIABLE_LENGTH_CLUSTER!(Context, ContextCluster); +DECLARE_VARIABLE_LENGTH_CLUSTER!(ContextScope, ContextScopeCluster); +DECLARE_VARIABLE_LENGTH_CLUSTER!(Mint, MintCluster); +DECLARE_VARIABLE_LENGTH_CLUSTER!(Float32x4, Float32x4Cluster); +DECLARE_VARIABLE_LENGTH_CLUSTER!(Float64x2, Float64x2Cluster); +DECLARE_VARIABLE_LENGTH_CLUSTER!(Record, RecordCluster); +DECLARE_VARIABLE_LENGTH_CLUSTER!(Array, ArrayCluster); +DECLARE_VARIABLE_LENGTH_CLUSTER!(WeakArray, WeakArrayCluster); +DECLARE_VARIABLE_LENGTH_CLUSTER!(ImmutableArray, ImmutableArrayCluster); +DECLARE_VARIABLE_LENGTH_CLUSTER!(ConstMap, ConstMapCluster); +DECLARE_VARIABLE_LENGTH_CLUSTER!(ConstSet, ConstSetCluster); +DECLARE_VARIABLE_LENGTH_CLUSTER!(CodeSourceMap, CodeSourceMapCluster); +DECLARE_VARIABLE_LENGTH_CLUSTER!(CompressedStackMaps, CompressedStackMapsCluster); impl Cluster for CompressedStackMapsCluster { fn is_fixed_len(&self) -> bool { @@ -414,7 +414,7 @@ impl Cluster for CompressedStackMapsCluster { } } -DECLARE_VARIABLE_LENGTH_CLUSTER!(PcDescriptors); +DECLARE_VARIABLE_LENGTH_CLUSTER!(PcDescriptors, PcDescriptorsCluster); impl Cluster for PcDescriptorsCluster { fn is_fixed_len(&self) -> bool { @@ -456,9 +456,9 @@ impl Cluster for PcDescriptorsCluster { } } -//DECLARE_VARIABLE_LENGTH_CLUSTER!(OneByteString); These only exist when NO COMPRESSED_POINTERS -//DECLARE_VARIABLE_LENGTH_CLUSTER!(TwoByteString); -DECLARE_VARIABLE_LENGTH_CLUSTER!(_String); +//DECLARE_VARIABLE_LENGTH_CLUSTER!(OneByteString, OneByteStringCluster); These only exist when NO COMPRESSED_POINTERS +//DECLARE_VARIABLE_LENGTH_CLUSTER!(TwoByteString, TwoByteStringCluster); +DECLARE_VARIABLE_LENGTH_CLUSTER!(_String, _StringCluster); impl Cluster for _StringCluster { fn is_fixed_len(&self) -> bool { diff --git a/crates/flutterdec-serwalker/src/snapshot.rs b/crates/flutterdec-serwalker/src/snapshot.rs index adf8b9e..4a803f7 100644 --- a/crates/flutterdec-serwalker/src/snapshot.rs +++ b/crates/flutterdec-serwalker/src/snapshot.rs @@ -58,8 +58,6 @@ pub struct DataSnapshot { end_of_alloc_area: usize, end_of_fill_area: usize, - - objects: Vec, } impl DataSnapshot { diff --git a/crates/flutterdec-serwalker/src/utils.rs b/crates/flutterdec-serwalker/src/utils.rs index 1445c27..827ecfb 100644 --- a/crates/flutterdec-serwalker/src/utils.rs +++ b/crates/flutterdec-serwalker/src/utils.rs @@ -2,27 +2,26 @@ use crate::constants::ClassId; #[macro_export] macro_rules! DECLARE_FIXED_LENGTH_CLUSTER { - ($name:ident, |$_self:ident, $stream:ident| $fill_impl:block) => { - ::paste::paste! { - pub struct [<$name Cluster>] - { - tags: u32, - cid: ClassId, - obj_count: u64, + ($name:ident, $cluster_name:ident, |$_self:ident, $stream:ident| $fill_impl:block) => { + pub struct $cluster_name + { + tags: u32, + cid: ClassId, + obj_count: u64, - start_of_fill: usize, - start_of_alloc: usize, + start_of_fill: usize, + start_of_alloc: usize, - end_of_fill: usize, - end_of_alloc: usize, + end_of_fill: usize, + end_of_alloc: usize, - first_ref_id: u32, + first_ref_id: u32, - objs: Vec> - } + objs: Vec> + } - impl Cluster for [<$name Cluster>] - { + impl Cluster for $cluster_name + { fn read_alloc(&mut self, last_ref_id: &mut u64, stream: &mut Stream) -> anyhow::Result { self.start_of_alloc = stream.get_current_pos(); @@ -61,16 +60,14 @@ macro_rules! DECLARE_FIXED_LENGTH_CLUSTER { } } - } }; } #[macro_export] macro_rules! DECLARE_VARIABLE_LENGTH_CLUSTER { - ($name:ident) => { - ::paste::paste! { - pub struct [<$name Cluster>] - { + ($name:ident, $cluster_name:ident) => { + pub struct $cluster_name + { tags: u32, cid: ClassId, obj_count: u64, @@ -83,8 +80,7 @@ macro_rules! DECLARE_VARIABLE_LENGTH_CLUSTER { first_ref_id: u32, - objs: Vec> - } + objs: Vec> } }; } From f50d581e8aebf7483c92843516c5fae5f0a7bbb5 Mon Sep 17 00:00:00 2001 From: kalixtez Date: Mon, 27 Jul 2026 20:45:37 -0500 Subject: [PATCH 37/50] chore(serwalker): removed trash files and ran cargo fmt --all --- Cargo.lock | 1 + crates/flutterdec-serwalker/Cargo.toml | 1 + .../flutterdec-serwalker/current_clusters.txt | Bin 40402 -> 0 bytes .../flutterdec-serwalker/raw_object_dump.txt | Bin 24688 -> 0 bytes .../flutterdec-serwalker/src/cluster/mod.rs | 140 +++++++++++------- crates/flutterdec-serwalker/src/lib.rs | 8 + crates/flutterdec-serwalker/src/utils.rs | 82 +++++----- 7 files changed, 132 insertions(+), 100 deletions(-) delete mode 100644 crates/flutterdec-serwalker/current_clusters.txt delete mode 100644 crates/flutterdec-serwalker/raw_object_dump.txt diff --git a/Cargo.lock b/Cargo.lock index 77fa0c4..b73c523 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -366,6 +366,7 @@ name = "flutterdec-serwalker" version = "0.1.0-alpha.2" dependencies = [ "anyhow", + "flutterdec-adapter", "goblin", ] diff --git a/crates/flutterdec-serwalker/Cargo.toml b/crates/flutterdec-serwalker/Cargo.toml index 281dfc9..442749f 100644 --- a/crates/flutterdec-serwalker/Cargo.toml +++ b/crates/flutterdec-serwalker/Cargo.toml @@ -7,3 +7,4 @@ license.workspace = true [dependencies] anyhow.workspace = true goblin.workspace = true +flutterdec-adapter = { path = "../flutterdec-adapter" } \ No newline at end of file diff --git a/crates/flutterdec-serwalker/current_clusters.txt b/crates/flutterdec-serwalker/current_clusters.txt deleted file mode 100644 index 536e6e72d57574a0232b5575067620c4dcf7e707..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 40402 zcmeI5X>T0I5r*e;fc%HG5!jR*F>(?Zf)5#%WZFb@L8P1nhC%ZdEmCAi$`T{#uP1q* zt}XTS>>NA0J+vGz2x<11?W*psTkDrR{R`Z{akDo zr^RQ*?~C6RllSq-LGf?hZ+DdM+Sczj)#7ewXS+C7n^4%)^<1x@^QyRA9O{!Z{jG}j zAH_xb33pu$pRehTZ;JcH*TtP;qqrA7d#v}>&)-zIP5FSQ7yA8iu~qye-nPYy(MbE4 z{tCWNb@yg*6z*Oty>I*79dUcRxLbTxJkqO;Vnu!U$G8V~b-k}1try?w8}53nzO3uh zN3A`mw)tIXeO3S8QX5d2wdIw---dYjXYo)it%bH$16Of=Pb3FhnkPnNQ?q|Qo-<^D z**?|#S3zDzdd1(KW_+YO&(z+Yt}k`hj$R+?`m9(gKF~M2dbd|x(S6s7oBDrCGjy!a zjBeVJ%>*rM>W)L*w;ftJ5@&GZtszY&UFW)&U+w975M+ELs(%0T;*;X9L7viiF5gq0 z@yI2zd{I0R2QPwLJQ9sFad9XrI$1wAW|X<}8l% znfN*oe|b4s3#~HWsl*@|%%p3pQ6CjwgxQU{xEyA6Q!~0G+4)kVJSzUAKV$%Y&-4oW zg>^$#Ug-ae@aYSEitHk>NGCRkUmX)JQdX{ht`hu z>q@vkj_r}CU{{WVG$JKP%X@0kHSe~xcrEDgL%mubTZ8+$BORY5eewrU=l{K+Qyc2Z zg=PtCydQ%TJG@;S$% z2>*;tnG54Nj>Sm#n_coY%OiDV%*p#f#_ITSsjl-U@2PG7zDeqlgDUsy;(nx2Iqsys zQSyjeo-Q>dT&3X8Yd5uVedFFE@kFhlWHIl~%TkL4&-Jh*! zpO=W6Ak z_?Kwnn~_WO+O!zk>Q-_qeCIguSkk&wJSg>lzzu@(uJI9k8)8>-kc5F&CradeDg2-tu^ivB1ZgMe_&dpCWFr(r{bi z9@uD^=Z)CpYKd>Bb5?x+mLittW${A=^?2LX=CU}l5kV`OJ$s7W+|Sq98*g_{^6Ciu1jDoY(N#GY)BG@@z8kD<6zYegozB_lFu2YzgwZN#Csk^lToVmJN-OKgh|0=cKEaWkb4ZUaFm5 zmww*p@P(dPi{tINP|pY3T3&Viz&-szG3jsfS^&3H3!mp0vt1(kYQa!(jIXlTN~U?6 zyqu#ShRxAZ!^T7!Rb9$U)iT6jjxuKTC?|SW%R1(zy)XPh>_Plw>%wH9;e{+D*=};? zd1+!3!46RIIn7If-hkY|_9pF_Bg1#`WNSR zLv!FW4$@+c4!=*_0p8BZ0d2Z(pS_=x9%wSw^oY+Q+^)QFmsg#nnA7L2N!vV^IlI?m zRgYfJv)+-jS9itDRzUKn$^^~cFT=h6KWjQ|`qY0#hdjYytB&?>lbrW~s@zQfL8iN! z!MuF$h(e63u?o2<*oq=+$Cg9M%l~=EKH!Ca)<3d@L#=eMB5B^gyynqm(fXo7G7*dw z7Gz(&!6I5cM$YJmoMySQBWK%qvD|Ij%DYG2Rop@)5B{>hWR0{U{JrW;9w^r_(i%85 zkW@F_5ARoXrSge2&qGx{aMus|t6SMkIZW|RzO}2N_;snvBj5SB^cnZcjOpbz#bnP_ zYw<}3Dw}RizS?dR)@zdoVy5Vk4SOQfR7$jI;&kXJ|LD7Pn_15WOIqf6IQ^>P zeOtG`5P~vfn|u5w>DfB^$$0j1(;E`~+v=+2e30qfyic}o>pWYUo%Re<|LC=hatxmn zo8hCWM9%A%V$25;$VVrqjejidV@#fnSA%bBEQl)>mfftA*$J!UL@L>XaujS9mY!Os z6G@Kkm}s-~O(^-jfUU1rqJW$;ys8}yi}mrcU-Qh>uOa(;PgaS2DXFZXrDm}fdg?nf zEK6Xhi+rI~#i4iYHDqb*JoF0on({VV;>h+ltZR1htBd*c{XXn&U~ib^AJ`w)jW?QX ztMA#ET%W0~2susm&1{P%C=GdP#{>Q8=Ctc~$H>(%`eTh78QFzBDA(w<&tKo@i>es1 z?dghaZZtewZC=%iu`@ee@69WFL~gcpoPBu{y&FI@RyC7qtx| z_LawS%$HZvEq8e)d1rR>wr#udY!@*V1XQjJNpG6UxvKUw<!Nll4r?-d`iFlt zPvZI9_C7YzAKv=<&h~{KS*@R)1Ck@FoYVeMT~XfIvzRqUKAyR#+xOU|^?mUz_p{fH zQ=wu-YP)9lrD4&ct=2~jXW5zD{c+C$;;p0W-{+>AMwE4wS%E>~FC*8Ia?>VHkTQ#urk@ti@^uP61FGjOJNMw1>UFz*_8n8JBLT&63P9er@iO?D`C84X)pr7B zvl^L&PO~3{Q!O0_?1i&wIiYw@noX?+XU?!fcBa!w(0-%~+#mNclUcD-j_t$}%L@Bx zU~cylblO5Pqz4$`@T>d}-!_@eeYm|LIv!DIdKNh?!nPElDvO(lz4&TP~ zQHExhop;Svuc@b0lRZ^0vCE6yDnAJmfc7{on^SJQK6XCr=lH?1BcN5=Q$k+AJYrr7 zCxQD^1Bapo<|eaeC#ex(*{Bg=wD+nh3Lkzg=8?R>epvlJS@;6U3VAd?Wisx>pgN=b z1i-$r8nR+f5nzW=yM**@n;j)oX}O=t+xoeX16AO|v|i=ib~jRA{#5p8*q#hYYr1`` zwzJ$%6~lIxBOLO_U5R(-^qz+zn=rS ztJlxQ+PWcYzdff8Ripp9lkWRHS%~xQ?*$9%nTG1Qb;%*J3&U@=%tIlQZ+RPPCNP%6+SG_xeJf z0hEu{ZShV%-ZXrub~$s&?0-HA_mp*j^L#C?XcpPMdLFzY8N?ylrjq=nGL-q+26r>Q ztd{3%HRcubwdr_Zh_+XStL>R(o+TZEis=isJGYOCE$8+&v`l2YrYk2{VOxgazWUs$ zmufLZui-g?O+Ba2JRxyey|?oP{j5%M?*7!My12^D7^VgZ86*b9dQ-W@socaR z>HZqe??j5!zu^7&RXK(=YF9-abTauZpN>Q|?prI*(Ig$qD3ziPo*oZ;0JW7YS@ zW!v{W)t|H%`#i}@9q*~cdyo9essih`{zOM>)B0r3vSVHEbLrtU`gSusVG?^1pA^(Q zd(MN=T-F>h@6(*5+LXtdZZ@56btKOux$J^u?RiQ4WgV?a@+S4!TWyxLkH%*^xt2`M zU%gyJJ{cdRiacX3ovM==dv4@3bgxNv;(cv;Z%>-^QJNgTsy+0*#+U!R(OK-9b6aN( zX0>i^4_r>{iDl{8qH*l~u5C#N`f;B4A0PMWtbw&HR=4x1tbDWs zYqBk-e@F6<{xoc~cDmoMj=jaV5)akJMjCT=3HtX4JxY4m|YI)mseb?j0 z=FxOZTyNu{l+t#qAAOFuP`yUmZf(CS*t1TiBCn|*8b&AEy-=BEmmiAv)_J0GPZ!v; zJQ^;mZc9u;FEbiVwQ3fP9*v*XY*nblD4XGYa4IBNRc4hp&CqzS{p!|*l{3zP^voeE zw}x$}t>Y@A#k6N`FTBNw66@xp%9+i}Wuo*H9Zxed^$~4Ef!-&-wrjs5OE5=!-064L zX?~>68O`(K&<8?3yTvV`AJ$3?9kKqfAF>A{)q2Gzud||JPbz0s#hxKEl00$;wHcg5 zPS*2*vao&{Ip>kzQM~_9pK>Dky52t$9b_a=57tASMNxEV$b+hM4r?sP`)tzTng@NPvh&Yxc+pF>12b@SAY zV`5~|jb!4zby%iMdlAKyN5kjdt?#T+OqVvh?d9yb`7NTyEg(W_06~aEES!7HD{vyY zw{jHorQUt3od1*J%T_z*oR;H;s2Nd%eZ9iGiD$TD{5Ki3blajy+I4f~bLLm$X*sNC zyimWXjmC?zf0$J?xL+z>>u;oQ$UqX;QRVl3Sh3_hlO28fp+=BZ-e00xVwx`vkEtMk~8{c?_zf0!b&_BlNntU!hb$H4Ei9gBs$mMQuX)H{Y>A-eXE>- zyQlj`p>5ws={mTpc9(l`>T>VM42ddZ6>=NpaRzmeoj~KMGN2di6XKJL9;;2Jkv?8s zSGSSX{Zg)f^dhdz_meEAo%X!E(r)@NxkK3VCTU$dy2Q*kt;{>U>eZtD>ZeWbz2``F zd3T!C?fbcIX`iMW^Xd37nP|qM^Jw{|WG+RvWeLQ`n1#poA$#ZJjKz<-MmhLVt#Fpc zHPuNVoujvMZI;$DZ+*17sw=K1@fmpNAsX{t(4xG$(udQ_&=<3DzRo>TRp{vLJ{ZPf zNwdKiGdsJQ1w*#vDW6F<`t-(iz{h`U96h;ToK`d2p0VaMny!&H{cOwl)?0-fv?t}w zII-~nV=QR~;O}$&jpQZV2F)_slho2Rpvmu@TGJR-KaY9N>NidAP3Cxl1$%LP{tXu) zb9hxyDbD}{S(&Eep{TyIJOHQ2QNza*tUyw%&*Njsk@AF2Y-hx3pd#{~_`Ckzbvi3u zwr<#rs<+3AvGbH#Lw2H4aWpylv8teH^!)R73t#_j^IS!QZW`{@U!LKv(-ueYed{UP Mqt-}q{_Okz08;eK%>V!Z diff --git a/crates/flutterdec-serwalker/raw_object_dump.txt b/crates/flutterdec-serwalker/raw_object_dump.txt deleted file mode 100644 index 00172a581a56c2512fc87fd5adee8e9fde15b84d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 24688 zcmd5^*=`)i747E$`3J&zNem-K76gH_09q_ZFeMWq<2(cg!JT7q!;o5-AitjEoIZ1P ztE%hHR1bR?fsne%>8`u4b!-0npSRs@chYU)|GVzE`wBnD`2Dmy=-zf0_}d@4pLRd# z|J~p(hj{X?yYFuLcXqqWZic`73IAr@Z@OP6Pyej@N59e>pS;7;9tqAzPL3 z=YVj8>sI$2ApMBv_1nMtC%5o-{x}{7-5K!y3V(as{jU3M_eKBQzx)4*^}}#@2Ko=M z{w>z8V{nDfFM*{nApKwT7#gPQT)*tT#2ULjH)h=xDBS7qNsp~(9FXkh-9Db13X?JL zPr>)AUPiO-8h0j(hj2phpFkJppy_@I^+V|U-FYvub8zGicZZPRIO&XmKEv7<(D5m# zmSo$ZU&BIA0r>=U?!&^a@c(aX#aQ+y&ETQsyCI^o@6<$3QXj&S#I9<)Qh zg~rjV+yI9by3A^pT?KtNSvj`43&1|?wQ~;roCX8I-_BN!F)$Cj4;~!!n9vJJBI`;* z80;;4`%Y!u7@u4C0Y(IRBE#*Wd@1o^bg22jqlM?8y;CdKr9agU!EW(EjFm@yoS?V5 zgZ$_RAI61N%4vn~0PiS+d<-0Jd%d{BC+7>P@EQHKTrOiMpFtvu(DXo;fJtvlO{5&? zi^j@A(7(Z(*YIZMztdcxO(rR3)Xl?%j2O$;^Hoz}R@W9vlKBrcnBLU&XCHr?BL{f< zk*_6Bb8R7i@~2~~J%GF}2Kqxh$g$;ml3Gi8FKnJ+Ra!<2o1fBST=maUU-m({-dYdx z7V;)$PJhR$2i=>lMN_VNzAAg7?J`eIqo(otSI91vTNkXcsgK|dZebDTYJf*z$rr~QoHqwq+7h`-5h{Rf1L#}nVpx~c9EoDYQ%#`8R`QWQA z!!6})PWdp0X@^Koe&{LkeCMI7(-vt-o?lJHseI+f;#LlI3XV-xWiiuGq*`5Xy@yN= z;5Xw!AU7`y0@54GkkjmkVn=#b0ttwEnlC z*F45f_TEeQ5x(VAO(mBX@QrD0x49bFc*qQoerH+5%e;WvJ1_W@%Y4~yf|lkK&O3Y0 zz55K!c!?_-u7WwpmW%!ASJ9FsA0&R|LVuqe`bfIIp=Rp0cZ z%<2z;F{4}*qsKst)^OZc-3i+2EaL{)Gp1Qy&Wxv4GkO1{^dy4Rl1tE9uHl&oi(N6s zQTeFWtfrMJc{&fI4xOX(Drzsr(GRKBXl4dJb5G z7kL}%j(QJ@`1IN2dd)JOpZ*3twVscyUNyC<7-lTFAIv>~V3hV9ug!?%)aGlM?Cfgz zr*Fh76YC@7z0x|*gmR6ZQgScAn_0KldkR*6*PxC5`vjfd#;XkX3vJqGy6^=0L|tH3z&)AOx=*<)@=_la51C1nI#R=9tj>G&nMOFv zU7TiCnWl@jM`7|}%#JmjJr!m}>{~D76lTQB*W^>? z?`ya$>k`Rx#(kx!{9#(XmLAb8(L5%#T|1&)drz5DQVw5~txsxz3fJpl=<)N(x*3MuD_Yp%ZPLY9@}>6 z9!h(ltb3v;lE&t`6`K8}RL6FAiwuzc$tk)?=ioFsJH=Y7d#20joJum^f}Z7F>_}3% z7P5n*egpF{_I66?i5{y_jz9Jzp7!x!*}kM|9P`m>HBpZd5u{~W;u(ndTuTbfhgb|4 z((--p8Q(9dSy)xGC&@UY3Om*&!W&OZnF*-3dJo^CSXc5ag0PyFAtd!bZ$KO4>2#3V z{BtRvmdr-IOH~fk+7f49Q+MLldMm5b6wSY_FNZV&C^9oYi)Ty5&|ujcefRQJU0ZA1 znaW>1gBH^V@8ZgefU)EiXuTOk6jAAYrA=2!#Ec%09X!pn9QPBW>HrJJcxpB3Sk&US z=Ly^uVkb}CRPJu1KCAX_S|5Cm=a0HS_C3HqPdcT$gLR+xeDHi;vqy|3=GEv2Iaf@M zsXuyOohBQ@o5!K=>p}PSFAGT!SI$^PQIoT*Mb> zQ0ILgC!#ssyGixaXvii>D^`!NbIIIY z6MM`JtPVW{Ozz@LJ4erom2u;0u{-q)+~f{LM49pyejW|lcKQ^+SHBRr|8o~6(}Z0-KCj4L`hQ#&o9HT8MypX{6i zeO$bj=Si#9qc@>V($CFsrAJWDuw1>4-&8r5Noxm~Xfn#?+9PR2)vfT%Etr*8EmPrM zDJO#!G2*TXH6!hJ_>)u9A5lxLm7>&bvlUjqy-oab{xu`_i7q);*q`=3E%N z4x*+Pw;b$JI2W^#79m*(-YtI zr-FF8kNf|!DehY7yq}%7qoj0F;H3K{?w{g`JNz)ev^9SOhv*ek`=J+Nm!8k?{Vl}k zUEJHw_ZFhFRdri+s*hP6&S2IwD9HN`A~dUm$Z5VPl;o6}~; zrN!iWV>x?Dh3`!(`jbB6Wi*{n-e^7{kLS|MRoOwDy|legp308t`MIYjdK`aoeOk^k z`e}-_)G*`f9W0l#gV8yPvNAR?DGX^8(ojqySIxpS#n_W2=4QsE(#XR`Z6E zqr4=w;n^VJ&H9!8+A|EJ%Q%tdtj<|`D!tY@VNck4jAx7_(w#H|NTY;3UC$NxZJ4-<+_SHwt6k^ie;dVp-%SaexyYrkq*W1hLkzfG9OB*6pe1d zF$Bvc+2N`|v(HOt?GU)CPuA98uvf$UvFz#B^e|t|Jy6@a(^njvn(&F_NLOiN^a0G_ zSPio#W)H@txk(>d&J2!$YFd?B%@_|(uf{C)9@n&wx|(8xZ9NXN*)h;d+GD@q(8@c} zUizolrl`-}Yh4GTQQTI4w?~GOGXGsPmYK{{zWrsQ|FxL{jwPQ_b&u&a3HYFjmt>JEe#F-fPx-OZnSw=yR9p@iYY2J{o zlAg!by&*=ne_vu|oVr`&+4P?EohRe1X~_`A>e-ESTEk|6y$?Vy=Cw~gS)tlJW+eReysZX2Tj4)FU zSf6Qf9*TSQm19qBJ#J2+`+WRVS+Sq7(2BDSd>_}#?%Bslu84z?Y%Ru3S?|4q2Ax5= z`EDe4H}y>`5su^)_ga)qO4^*z=FE%L5%gA@(8pcWDTm12)%Vx~4&>NsoNc$6bpXA3 zOQmD)@EdB5@D69SNOPJ`PAQLVaGt$8p48ztH+&OD;eu*7S#OTeigQMr0`Tig4f@!I?@fqB1wp#DE2_A+>5 z)=tj?#;0)Dp|JUKM!-|(8l}RT@4DaB&}qfZ?D*3|vl{Wanc@6Rq)p7T^K+J^Y{ady zmWZVxZ^qsOWkL&X`(eKu#| zXUKU7JCYVB>uw-jebw)dd#=O)nt$+VaBzvtr zURIiq*xdRh{@O$1)Z!c1O-aqe@t=DA&F`97w~;x8CSG~6@IKUG&b16VXS$VN#m}}L zbUrs}^T5GX4$2zEMtoDVac-G9Wjq%Z$HkS!%O4?m&g*BscEL$ Lv#OG=aXR=v#PvP! diff --git a/crates/flutterdec-serwalker/src/cluster/mod.rs b/crates/flutterdec-serwalker/src/cluster/mod.rs index 3979f92..b78a7dc 100644 --- a/crates/flutterdec-serwalker/src/cluster/mod.rs +++ b/crates/flutterdec-serwalker/src/cluster/mod.rs @@ -84,17 +84,21 @@ DECLARE_FIXED_LENGTH_CLUSTER!(ClosureData, ClosureDataCluster, |_self, stream| { obj.packed_fields = stream.read_unsigned()? as u32; } }); -DECLARE_FIXED_LENGTH_CLUSTER!(FfiTrampolineData, FfiTrampolineDataCluster, |_self, stream| { - for obj_idx in 0.._self.obj_count as usize { - let obj = &mut *_self.objs[obj_idx]; - obj.signature_type = stream.read_ref_id()?; - obj.c_signature = stream.read_ref_id()?; - obj.callback_target = stream.read_ref_id()?; - obj.callback_exceptional_return = stream.read_ref_id()?; - obj.ffi_function_kind = stream.read_unsigned()? as u8; - obj.callback_id = stream.read()? as i32; +DECLARE_FIXED_LENGTH_CLUSTER!( + FfiTrampolineData, + FfiTrampolineDataCluster, + |_self, stream| { + for obj_idx in 0.._self.obj_count as usize { + let obj = &mut *_self.objs[obj_idx]; + obj.signature_type = stream.read_ref_id()?; + obj.c_signature = stream.read_ref_id()?; + obj.callback_target = stream.read_ref_id()?; + obj.callback_exceptional_return = stream.read_ref_id()?; + obj.ffi_function_kind = stream.read_unsigned()? as u8; + obj.callback_id = stream.read()? as i32; + } } -}); +); DECLARE_FIXED_LENGTH_CLUSTER!(Field, FieldCluster, |_self, stream| { for obj_idx in 0.._self.obj_count as usize { let obj = &mut *_self.objs[obj_idx]; @@ -152,22 +156,26 @@ DECLARE_FIXED_LENGTH_CLUSTER!(Namespace, NamespaceCluster, |_self, stream| { obj.owner = stream.read_ref_id()?; } }); -DECLARE_FIXED_LENGTH_CLUSTER!(KernelProgramInfo, KernelProgramInfoCluster, |_self, stream| { - for obj_idx in 0.._self.obj_count as usize { - let obj = &mut *_self.objs[obj_idx]; - obj.kernel_component = stream.read_ref_id()?; - obj.string_offsets = stream.read_ref_id()?; - obj.string_data = stream.read_ref_id()?; - obj.canonical_names = stream.read_ref_id()?; - obj.metadata_payloads = stream.read_ref_id()?; - obj.metadata_mappings = stream.read_ref_id()?; - obj.scripts = stream.read_ref_id()?; - obj.constants = stream.read_ref_id()?; - obj.constants_table = stream.read_ref_id()?; - obj.libraries_cache = stream.read_ref_id()?; - obj.classes_cache = stream.read_ref_id()?; +DECLARE_FIXED_LENGTH_CLUSTER!( + KernelProgramInfo, + KernelProgramInfoCluster, + |_self, stream| { + for obj_idx in 0.._self.obj_count as usize { + let obj = &mut *_self.objs[obj_idx]; + obj.kernel_component = stream.read_ref_id()?; + obj.string_offsets = stream.read_ref_id()?; + obj.string_data = stream.read_ref_id()?; + obj.canonical_names = stream.read_ref_id()?; + obj.metadata_payloads = stream.read_ref_id()?; + obj.metadata_mappings = stream.read_ref_id()?; + obj.scripts = stream.read_ref_id()?; + obj.constants = stream.read_ref_id()?; + obj.constants_table = stream.read_ref_id()?; + obj.libraries_cache = stream.read_ref_id()?; + obj.classes_cache = stream.read_ref_id()?; + } } -}); +); DECLARE_FIXED_LENGTH_CLUSTER!(UnlinkedCall, UnlinkedCallCluster, |_self, stream| { for obj_idx in 0.._self.obj_count as usize { let obj = &mut *_self.objs[obj_idx]; @@ -183,24 +191,32 @@ DECLARE_FIXED_LENGTH_CLUSTER!(ICData, ICDataCluster, |_self, stream| { obj.state_bits = stream.read_unsigned()? as u32; } }); -DECLARE_FIXED_LENGTH_CLUSTER!(MegamorphicCache, MegamorphicCacheCluster, |_self, stream| { - for obj_idx in 0.._self.obj_count as usize { - let obj = &mut *_self.objs[obj_idx]; - obj.target_name = stream.read_ref_id()?; - obj.args_descriptor = stream.read_ref_id()?; - obj.buckets = stream.read_ref_id()?; - obj.mask = stream.read_ref_id()? as i32; - obj.filled_entry_count = stream.read()? as i32; +DECLARE_FIXED_LENGTH_CLUSTER!( + MegamorphicCache, + MegamorphicCacheCluster, + |_self, stream| { + for obj_idx in 0.._self.obj_count as usize { + let obj = &mut *_self.objs[obj_idx]; + obj.target_name = stream.read_ref_id()?; + obj.args_descriptor = stream.read_ref_id()?; + obj.buckets = stream.read_ref_id()?; + obj.mask = stream.read_ref_id()? as i32; + obj.filled_entry_count = stream.read()? as i32; + } } -}); -DECLARE_FIXED_LENGTH_CLUSTER!(SubtypeTestCache, SubtypeTestCacheCluster, |_self, stream| { - for obj_idx in 0.._self.obj_count as usize { - let obj = &mut *_self.objs[obj_idx]; - obj.cache = stream.read_ref_id()?; - obj.num_inputs = stream.read_unsigned()? as u32; - obj.num_occupied = stream.read_unsigned()? as u32; +); +DECLARE_FIXED_LENGTH_CLUSTER!( + SubtypeTestCache, + SubtypeTestCacheCluster, + |_self, stream| { + for obj_idx in 0.._self.obj_count as usize { + let obj = &mut *_self.objs[obj_idx]; + obj.cache = stream.read_ref_id()?; + obj.num_inputs = stream.read_unsigned()? as u32; + obj.num_occupied = stream.read_unsigned()? as u32; + } } -}); +); DECLARE_FIXED_LENGTH_CLUSTER!(LoadingUnit, LoadingUnitCluster, |_self, stream| { for obj_idx in 0.._self.obj_count as usize { let obj = &mut *_self.objs[obj_idx]; @@ -221,13 +237,17 @@ DECLARE_FIXED_LENGTH_CLUSTER!(LanguageError, LanguageErrorCluster, |_self, strea obj.kind = stream.read()? as i8; } }); -DECLARE_FIXED_LENGTH_CLUSTER!(UnhandledException, UnhandledExceptionCluster, |_self, stream| { - for obj_idx in 0.._self.obj_count as usize { - let obj = &mut *_self.objs[obj_idx]; - obj.exception = stream.read_ref_id()?; - obj.stacktrace = stream.read_ref_id()?; +DECLARE_FIXED_LENGTH_CLUSTER!( + UnhandledException, + UnhandledExceptionCluster, + |_self, stream| { + for obj_idx in 0.._self.obj_count as usize { + let obj = &mut *_self.objs[obj_idx]; + obj.exception = stream.read_ref_id()?; + obj.stacktrace = stream.read_ref_id()?; + } } -}); +); DECLARE_FIXED_LENGTH_CLUSTER!(LibraryPrefix, LibraryPrefixCluster, |_self, stream| { for obj_idx in 0.._self.obj_count as usize { let obj = &mut *_self.objs[obj_idx]; @@ -303,14 +323,18 @@ DECLARE_FIXED_LENGTH_CLUSTER!(Double, DoubleCluster, |_self, stream| { DECLARE_FIXED_LENGTH_CLUSTER!(Int32x4, Int32x4Cluster, |_self, stream| { for _ in 0.._self.obj_count as usize {} }); -DECLARE_FIXED_LENGTH_CLUSTER!(GrowableObjectArray, GrowableObjectArrayCluster, |_self, stream| { - for obj_idx in 0.._self.obj_count as usize { - let obj = &mut *_self.objs[obj_idx]; - obj.type_arguments = stream.read_ref_id()?; - obj.data = stream.read_ref_id()?; - obj.length = stream.read_ref_id()? as i32; +DECLARE_FIXED_LENGTH_CLUSTER!( + GrowableObjectArray, + GrowableObjectArrayCluster, + |_self, stream| { + for obj_idx in 0.._self.obj_count as usize { + let obj = &mut *_self.objs[obj_idx]; + obj.type_arguments = stream.read_ref_id()?; + obj.data = stream.read_ref_id()?; + obj.length = stream.read_ref_id()? as i32; + } } -}); +); DECLARE_FIXED_LENGTH_CLUSTER!(TypedDataView, TypedDataViewCluster, |_self, stream| { for obj_idx in 0.._self.obj_count as usize { let obj = &mut *_self.objs[obj_idx]; @@ -318,9 +342,11 @@ DECLARE_FIXED_LENGTH_CLUSTER!(TypedDataView, TypedDataViewCluster, |_self, strea obj.offset_in_bytes = stream.read_ref_id()? as i32; } }); -DECLARE_FIXED_LENGTH_CLUSTER!(ExternalTypedData, ExternalTypedDataCluster, |_self, stream| { - for _ in 0.._self.obj_count as usize {} -}); +DECLARE_FIXED_LENGTH_CLUSTER!( + ExternalTypedData, + ExternalTypedDataCluster, + |_self, stream| { for _ in 0.._self.obj_count as usize {} } +); DECLARE_FIXED_LENGTH_CLUSTER!(StackTrace, StackTraceCluster, |_self, stream| { for obj_idx in 0.._self.obj_count as usize { let obj = &mut *_self.objs[obj_idx]; diff --git a/crates/flutterdec-serwalker/src/lib.rs b/crates/flutterdec-serwalker/src/lib.rs index 1197ae1..e124d0c 100644 --- a/crates/flutterdec-serwalker/src/lib.rs +++ b/crates/flutterdec-serwalker/src/lib.rs @@ -7,3 +7,11 @@ mod raw_object; mod snapshot; mod stream; + +use flutterdec_adapter::ProgramModel; + +fn walk_and_enrich(data_snapshot: &[u8], instruction_snapshot: &[u8]) -> ProgramModel { + let program_model: ProgramModel; + + program_model +} diff --git a/crates/flutterdec-serwalker/src/utils.rs b/crates/flutterdec-serwalker/src/utils.rs index 827ecfb..0481581 100644 --- a/crates/flutterdec-serwalker/src/utils.rs +++ b/crates/flutterdec-serwalker/src/utils.rs @@ -3,8 +3,7 @@ use crate::constants::ClassId; #[macro_export] macro_rules! DECLARE_FIXED_LENGTH_CLUSTER { ($name:ident, $cluster_name:ident, |$_self:ident, $stream:ident| $fill_impl:block) => { - pub struct $cluster_name - { + pub struct $cluster_name { tags: u32, cid: ClassId, obj_count: u64, @@ -17,70 +16,67 @@ macro_rules! DECLARE_FIXED_LENGTH_CLUSTER { first_ref_id: u32, - objs: Vec> + objs: Vec>, } - impl Cluster for $cluster_name - { - fn read_alloc(&mut self, last_ref_id: &mut u64, stream: &mut Stream) -> anyhow::Result - { - self.start_of_alloc = stream.get_current_pos(); - self.first_ref_id = *last_ref_id as u32; + impl Cluster for $cluster_name { + fn read_alloc( + &mut self, + last_ref_id: &mut u64, + stream: &mut Stream, + ) -> anyhow::Result { + self.start_of_alloc = stream.get_current_pos(); + self.first_ref_id = *last_ref_id as u32; - self.obj_count = stream.read_unsigned()?; + self.obj_count = stream.read_unsigned()?; - for _obj_idx in 0..self.obj_count - { - self.objs.push(Box::<$name >::default()); - } - - *last_ref_id += self.obj_count; - self.end_of_alloc = stream.get_current_pos(); - - Ok(self.end_of_alloc - self.start_of_alloc) + for _obj_idx in 0..self.obj_count { + self.objs.push(Box::<$name>::default()); } - fn read_fill(&mut self, stream: &mut Stream) -> anyhow::Result - { - self.start_of_fill = stream.get_current_pos(); + *last_ref_id += self.obj_count; + self.end_of_alloc = stream.get_current_pos(); - let $_self = self; - let $stream = stream; + Ok(self.end_of_alloc - self.start_of_alloc) + } - $fill_impl; + fn read_fill(&mut self, stream: &mut Stream) -> anyhow::Result { + self.start_of_fill = stream.get_current_pos(); - $_self.end_of_fill = $stream.get_current_pos(); + let $_self = self; + let $stream = stream; - Ok($_self.end_of_fill - $_self.start_of_fill) - } + $fill_impl; - fn is_fixed_len(&self) -> bool - { - true - } + $_self.end_of_fill = $stream.get_current_pos(); + + Ok($_self.end_of_fill - $_self.start_of_fill) } + fn is_fixed_len(&self) -> bool { + true + } + } }; } #[macro_export] macro_rules! DECLARE_VARIABLE_LENGTH_CLUSTER { ($name:ident, $cluster_name:ident) => { - pub struct $cluster_name - { - tags: u32, - cid: ClassId, - obj_count: u64, + pub struct $cluster_name { + tags: u32, + cid: ClassId, + obj_count: u64, - start_of_fill: usize, - start_of_alloc: usize, + start_of_fill: usize, + start_of_alloc: usize, - end_of_fill: usize, - end_of_alloc: usize, + end_of_fill: usize, + end_of_alloc: usize, - first_ref_id: u32, + first_ref_id: u32, - objs: Vec> + objs: Vec>, } }; } From 5956cf453886478c69f2e6f31ebfff03dbe242ef Mon Sep 17 00:00:00 2001 From: kalixtez Date: Mon, 27 Jul 2026 21:27:46 -0500 Subject: [PATCH 38/50] fix(serwalker): first reference id is now num_base_objects + 1 --- crates/flutterdec-serwalker/src/snapshot.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/flutterdec-serwalker/src/snapshot.rs b/crates/flutterdec-serwalker/src/snapshot.rs index 4a803f7..d55f676 100644 --- a/crates/flutterdec-serwalker/src/snapshot.rs +++ b/crates/flutterdec-serwalker/src/snapshot.rs @@ -92,7 +92,7 @@ impl DataSnapshot { } fn parse_clusters(&mut self, stream: &mut Stream) -> anyhow::Result<()> { - let mut curr_ref_id: u64 = 0; // all objects are numbered starting from 0 + let mut curr_ref_id: u64 = self.num_base_objects + 1; // all objects are numbered starting from 0 self.start_of_alloc_area = stream.get_current_pos(); for _cluster_idx in 0..self.num_clusters { From 5ef74e1b48ca2a7f349f10c208b59bb8c51d9a8e Mon Sep 17 00:00:00 2001 From: kalixtez Date: Mon, 27 Jul 2026 22:28:37 -0500 Subject: [PATCH 39/50] fix(serwalker): fixed Read and Read being read as read_unsigned and read, they are now correctly read as normal u8/i8 with read_byte --- .../flutterdec-serwalker/src/cluster/mod.rs | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/crates/flutterdec-serwalker/src/cluster/mod.rs b/crates/flutterdec-serwalker/src/cluster/mod.rs index b78a7dc..c7d5bb8 100644 --- a/crates/flutterdec-serwalker/src/cluster/mod.rs +++ b/crates/flutterdec-serwalker/src/cluster/mod.rs @@ -94,7 +94,7 @@ DECLARE_FIXED_LENGTH_CLUSTER!( obj.c_signature = stream.read_ref_id()?; obj.callback_target = stream.read_ref_id()?; obj.callback_exceptional_return = stream.read_ref_id()?; - obj.ffi_function_kind = stream.read_unsigned()? as u8; + obj.ffi_function_kind = stream.read_byte()? as u8; obj.callback_id = stream.read()? as i32; } } @@ -142,8 +142,8 @@ DECLARE_FIXED_LENGTH_CLUSTER!(Library, LibraryCluster, |_self, stream| { // obj.loaded_scripts = stream.read_ref_id()?; [[NOT PRESENT IN FullAOT]] obj.index = stream.read()? as i32; obj.num_imports = stream.read()? as u16; - obj.load_state = stream.read()? as i8; - obj.flags = stream.read()? as u8; + obj.load_state = stream.read_byte()? as i8; + obj.flags = stream.read_byte()? as u8; // obj.kernel_library_index = stream.read_unsigned()? as u32; [[NOT PRESENT IN FullAOT]] } }); @@ -179,7 +179,7 @@ DECLARE_FIXED_LENGTH_CLUSTER!( DECLARE_FIXED_LENGTH_CLUSTER!(UnlinkedCall, UnlinkedCallCluster, |_self, stream| { for obj_idx in 0.._self.obj_count as usize { let obj = &mut *_self.objs[obj_idx]; - obj.can_patch_to_monomorphic = stream.read_unsigned()? != 0; + obj.can_patch_to_monomorphic = stream.read_byte()? != 0; } }); DECLARE_FIXED_LENGTH_CLUSTER!(ICData, ICDataCluster, |_self, stream| { @@ -233,8 +233,8 @@ DECLARE_FIXED_LENGTH_CLUSTER!(LanguageError, LanguageErrorCluster, |_self, strea obj.message = stream.read_ref_id()?; obj.formatted_message = stream.read_ref_id()?; obj.token_pos = stream.read()? as i32; - obj.report_after_token = stream.read()? != 0; - obj.kind = stream.read()? as i8; + obj.report_after_token = stream.read_byte()? != 0; + obj.kind = stream.read_byte()? as i8; } }); DECLARE_FIXED_LENGTH_CLUSTER!( @@ -255,7 +255,7 @@ DECLARE_FIXED_LENGTH_CLUSTER!(LibraryPrefix, LibraryPrefixCluster, |_self, strea obj.imports = stream.read_ref_id()?; obj.importer = stream.read_ref_id()?; obj.num_imports = stream.read_unsigned()? as u16; - obj.is_deferred_load = stream.read_unsigned()? != 0; + obj.is_deferred_load = stream.read_byte()? != 0; } }); DECLARE_FIXED_LENGTH_CLUSTER!(Type, TypeCluster, |_self, stream| { @@ -264,7 +264,7 @@ DECLARE_FIXED_LENGTH_CLUSTER!(Type, TypeCluster, |_self, stream| { obj.type_test_stub = stream.read_ref_id()?; obj.hash = stream.read_ref_id()?; obj.arguments = stream.read_ref_id()?; - obj.flags = stream.read_unsigned()? as u8; + obj.flags = stream.read_byte()? as u8; } }); DECLARE_FIXED_LENGTH_CLUSTER!(FunctionType, FunctionTypeCluster, |_self, stream| { @@ -276,7 +276,7 @@ DECLARE_FIXED_LENGTH_CLUSTER!(FunctionType, FunctionTypeCluster, |_self, stream| obj.result_type = stream.read_ref_id()?; obj.parameter_types = stream.read_ref_id()?; obj.named_parameter_names = stream.read_ref_id()?; - obj.flags = stream.read()? as u8; + obj.flags = stream.read_byte()? as u8; obj.packed_parameter_counts = stream.read_unsigned()? as u32; obj.packed_type_parameter_counts = stream.read_unsigned()? as u16; } @@ -288,7 +288,7 @@ DECLARE_FIXED_LENGTH_CLUSTER!(RecordType, RecordTypeCluster, |_self, stream| { obj.hash = stream.read_ref_id()?; obj.shape = stream.read_ref_id()? as i32; obj.field_types = stream.read_ref_id()?; - obj.flags = stream.read()? as u8; + obj.flags = stream.read_byte()? as u8; // obj.shape = stream.read_ref_id()?; as i32; } }); @@ -300,7 +300,7 @@ DECLARE_FIXED_LENGTH_CLUSTER!(TypeParameter, TypeParameterCluster, |_self, strea obj.owner = stream.read_ref_id()?; obj.base = stream.read()? as u16; obj.index = stream.read()? as u16; - obj.flags = stream.read()? as u8; + obj.flags = stream.read_byte()? as u8; } }); DECLARE_FIXED_LENGTH_CLUSTER!(Closure, ClosureCluster, |_self, stream| { From b506d4978d4e00df7d7bb6aa0485e57493448e98 Mon Sep 17 00:00:00 2001 From: kalixtez Date: Mon, 27 Jul 2026 22:31:56 -0500 Subject: [PATCH 40/50] fix(serwalker): erroneously changed obj.flags for TypeCluster which was correct to begin with, rolled back --- crates/flutterdec-serwalker/src/cluster/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/flutterdec-serwalker/src/cluster/mod.rs b/crates/flutterdec-serwalker/src/cluster/mod.rs index c7d5bb8..9110db7 100644 --- a/crates/flutterdec-serwalker/src/cluster/mod.rs +++ b/crates/flutterdec-serwalker/src/cluster/mod.rs @@ -264,7 +264,7 @@ DECLARE_FIXED_LENGTH_CLUSTER!(Type, TypeCluster, |_self, stream| { obj.type_test_stub = stream.read_ref_id()?; obj.hash = stream.read_ref_id()?; obj.arguments = stream.read_ref_id()?; - obj.flags = stream.read_byte()? as u8; + obj.flags = stream.read_unsigned()? as u8; } }); DECLARE_FIXED_LENGTH_CLUSTER!(FunctionType, FunctionTypeCluster, |_self, stream| { From 501911218156e1cc0b677e5c6ba2927b635733a7 Mon Sep 17 00:00:00 2001 From: kalixtez Date: Mon, 27 Jul 2026 22:39:38 -0500 Subject: [PATCH 41/50] fix(serwalker): Function.kind_tag was being read as an unsigned despite being routed through the signed byte marker in C+=, commented out Function.token_pos as it is NOT present in release builds --- crates/flutterdec-serwalker/src/cluster/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/flutterdec-serwalker/src/cluster/mod.rs b/crates/flutterdec-serwalker/src/cluster/mod.rs index 9110db7..4b16fde 100644 --- a/crates/flutterdec-serwalker/src/cluster/mod.rs +++ b/crates/flutterdec-serwalker/src/cluster/mod.rs @@ -70,9 +70,9 @@ DECLARE_FIXED_LENGTH_CLUSTER!(Function, FunctionCluster, |_self, stream| { // obj.unoptimized_code = stream.read_ref_id()?; [[NOT PRESENT IN FullAOT]] // obj.bitmap = stream.read_unsigned()? as u64; [[NOT PRESENT IN FullAOT]] obj.code_index = stream.read_unsigned()? as u32; - obj.token_pos = stream.read()? as i32; + // obj.token_pos = stream.read()? as i32; [[NOT PRESENT IN release builds (called prouct in Flutter)]] // obj.kernel_offset = stream.read_unsigned()? as u32; [[NOT PRESENT IN FullAOT]] - obj.kind_tag = stream.read_unsigned()? as u32; + obj.kind_tag = stream.read()? as u32; } }); DECLARE_FIXED_LENGTH_CLUSTER!(ClosureData, ClosureDataCluster, |_self, stream| { From 6519b873535a87859de421e43cbf97be12576c48 Mon Sep 17 00:00:00 2001 From: Kl <64610615+kalixtez@users.noreply.github.com> Date: Mon, 27 Jul 2026 22:52:41 -0500 Subject: [PATCH 42/50] Update crates/flutterdec-serwalker/src/snapshot.rs fix(serwalker): check Dart's post-alloc ref count invariant next_ref_index - kFirstReference must equal num_objects once every cluster has allocated. Failing loudly beats parsing on into garbage. Co-authored-by: Camilo Vera Vidales --- crates/flutterdec-serwalker/src/snapshot.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/crates/flutterdec-serwalker/src/snapshot.rs b/crates/flutterdec-serwalker/src/snapshot.rs index d55f676..bb9a63f 100644 --- a/crates/flutterdec-serwalker/src/snapshot.rs +++ b/crates/flutterdec-serwalker/src/snapshot.rs @@ -115,6 +115,16 @@ impl DataSnapshot { } self.end_of_alloc_area = stream.get_current_pos(); + // ASSERT_EQUAL(next_ref_index_ - kFirstReference, num_objects_) + // app_snapshot.cc:9591. Cheapest possible desync detector. + let allocated = curr_ref_id - 1; + if allocated != self.num_objects { + anyhow::bail!( + "alloc pass allocated {allocated} refs, header declares {}", + self.num_objects + ); + } + self.start_of_fill_area = stream.get_current_pos(); for key in self.cluster_order.iter() { let cluster = self.clusters.get_mut(key).unwrap(); From 81a0b145f46be5ed3a187117a70ca06fec36d000 Mon Sep 17 00:00:00 2001 From: kalixtez Date: Tue, 28 Jul 2026 02:59:54 -0500 Subject: [PATCH 43/50] feat(serwalker): added multiple to-do place holder (function declarations) for the future object store and information producer modules, defined the ObjectStore and IsolateObjectStore structs --- .../src/info_producer/mod.rs | 25 ++ crates/flutterdec-serwalker/src/lib.rs | 21 +- .../src/object_store/mod.rs | 276 ++++++++++++++++++ crates/flutterdec-serwalker/src/snapshot.rs | 8 +- crates/flutterdec-serwalker/src/stream.rs | 8 +- 5 files changed, 330 insertions(+), 8 deletions(-) create mode 100644 crates/flutterdec-serwalker/src/info_producer/mod.rs diff --git a/crates/flutterdec-serwalker/src/info_producer/mod.rs b/crates/flutterdec-serwalker/src/info_producer/mod.rs new file mode 100644 index 0000000..c3761f0 --- /dev/null +++ b/crates/flutterdec-serwalker/src/info_producer/mod.rs @@ -0,0 +1,25 @@ +use anyhow::Error; +use flutterdec_adapter::ProgramModel; + +use crate::snapshot::DataSnapshot; + +pub fn enrich_model_headers( + model: &mut ProgramModel, + snapshot: &DataSnapshot, +) -> anyhow::Result<()> { + Ok(()) +} + +pub fn enrich_model_object_info( + model: &mut ProgramModel, + snapshot: &DataSnapshot, +) -> anyhow::Result<()> { + Ok(()) +} + +pub fn enrich_model_object_pool( + model: &mut ProgramModel, + snapshot: &DataSnapshot, +) -> anyhow::Result<()> { + Ok(()) +} diff --git a/crates/flutterdec-serwalker/src/lib.rs b/crates/flutterdec-serwalker/src/lib.rs index e124d0c..cb37d28 100644 --- a/crates/flutterdec-serwalker/src/lib.rs +++ b/crates/flutterdec-serwalker/src/lib.rs @@ -6,12 +6,31 @@ mod object_store; mod raw_object; mod snapshot; +mod info_producer; mod stream; use flutterdec_adapter::ProgramModel; -fn walk_and_enrich(data_snapshot: &[u8], instruction_snapshot: &[u8]) -> ProgramModel { +use crate::{ + info_producer::{enrich_model_headers, enrich_model_object_info}, + snapshot::parse_snapshot, + stream::Stream, +}; + +fn walk_snapshot_and_enrich_model( + isolate_data: &[u8], + isolate_instr: &[u8], + vm_data: Option<&[u8]>, + vm_instr: Option<&[u8]>, +) -> ProgramModel { let program_model: ProgramModel; + let mut isolate_data_stream = Stream::new(isolate_data); + let isolate_data_snapshot = parse_snapshot(&mut isolate_data_stream)?; + + enrich_model_headers(&mut program_model, &isolate_data_snapshot); + enrich_model_object_info(&mut program_model, &isolate_data_snapshot); + enrich_model_object_pool(&mut program_model, &isolate_data_snapshot); + program_model } diff --git a/crates/flutterdec-serwalker/src/object_store/mod.rs b/crates/flutterdec-serwalker/src/object_store/mod.rs index 8b13789..5e5176f 100644 --- a/crates/flutterdec-serwalker/src/object_store/mod.rs +++ b/crates/flutterdec-serwalker/src/object_store/mod.rs @@ -1 +1,277 @@ +struct ObjectStore { + list_class: u32, // ClassPtr + map_class: u32, // ClassPtr + set_class: u32, // ClassPtr + non_nullable_list_rare_type: u32, // TypePtr + non_nullable_map_rare_type: u32, // TypePtr + enum_index_field: u32, // FieldPtr + enum_name_field: u32, // FieldPtr + _object_equals_function: u32, // FunctionPtr + _object_hash_code_function: u32, // FunctionPtr + _object_to_string_function: u32, // FunctionPtr + symbol_class: u32, // ClassPtr + symbol_name_field: u32, // FieldPtr + ffi_array_class: u32, // ClassPtr + ffi_compound_class: u32, // ClassPtr + ffi_struct_class: u32, // ClassPtr + ffi_union_class: u32, // ClassPtr + ffi_varargs_class: u32, // ClassPtr + compound_offset_in_bytes_field: u32, // FieldPtr + compound_typed_data_base_field: u32, // FieldPtr + ffi_resolver_function: u32, // FunctionPtr + handle_finalizer_message_function: u32, // FunctionPtr + handle_native_finalizer_message_function: u32, // FunctionPtr + non_nullable_future_never_type: u32, // TypePtr + nullable_future_null_type: u32, // TypePtr + send_port_class: u32, // ClassPtr + capability_class: u32, // ClassPtr + transferable_class: u32, // ClassPtr + lookup_port_handler: u32, // FunctionPtr + lookup_open_ports: u32, // FunctionPtr + handle_message_function: u32, // FunctionPtr + object_class: u32, // ClassPtr + object_type: u32, // TypePtr + non_nullable_object_type: u32, // TypePtr + nullable_object_type: u32, // TypePtr + null_class: u32, // ClassPtr + null_type: u32, // TypePtr + never_class: u32, // ClassPtr + never_type: u32, // TypePtr + function_type: u32, // TypePtr + type_type: u32, // TypePtr + closure_class: u32, // ClassPtr + record_class: u32, // ClassPtr + number_type: u32, // TypePtr + nullable_number_type: u32, // TypePtr + int_type: u32, // TypePtr + non_nullable_int_type: u32, // TypePtr + nullable_int_type: u32, // TypePtr + integer_implementation_class: u32, // ClassPtr + int64_type: u32, // TypePtr + smi_class: u32, // ClassPtr + smi_type: u32, // TypePtr + mint_class: u32, // ClassPtr + mint_type: u32, // TypePtr + double_class: u32, // ClassPtr + double_type: u32, // TypePtr + nullable_double_type: u32, // TypePtr + float32x4_type: u32, // TypePtr + int32x4_type: u32, // TypePtr + float64x2_type: u32, // TypePtr + string_type: u32, // TypePtr + type_argument_int: u32, // TypeArgumentsPtr + type_argument_double: u32, // TypeArgumentsPtr + type_argument_never: u32, // TypeArgumentsPtr + type_argument_string: u32, // TypeArgumentsPtr + type_argument_string_dynamic: u32, // TypeArgumentsPtr + type_argument_string_string: u32, // TypeArgumentsPtr + compiletime_error_class: u32, // ClassPtr + pragma_class: u32, // ClassPtr + pragma_name: u32, // FieldPtr + pragma_options: u32, // FieldPtr + future_class: u32, // ClassPtr + future_or_class: u32, // ClassPtr + one_byte_string_class: u32, // ClassPtr + two_byte_string_class: u32, // ClassPtr + bool_type: u32, // TypePtr + bool_class: u32, // ClassPtr + array_class: u32, // ClassPtr + array_type: u32, // TypePtr + immutable_array_class: u32, // ClassPtr + growable_object_array_class: u32, // ClassPtr + map_impl_class: u32, // ClassPtr + const_map_impl_class: u32, // ClassPtr + set_impl_class: u32, // ClassPtr + const_set_impl_class: u32, // ClassPtr + float32x4_class: u32, // ClassPtr + int32x4_class: u32, // ClassPtr + float64x2_class: u32, // ClassPtr + error_class: u32, // ClassPtr + expando_class: u32, // ClassPtr + iterable_class: u32, // ClassPtr + weak_property_class: u32, // ClassPtr + weak_reference_class: u32, // ClassPtr + finalizer_class: u32, // ClassPtr + finalizer_entry_class: u32, // ClassPtr + native_finalizer_class: u32, // ClassPtr + dart_condition_variable_class: u32, // ClassPtr + dart_mutex_class: u32, // ClassPtr + symbol_table: u32, // WeakArrayPtr + regexp_table: u32, // WeakArrayPtr + canonical_types: u32, // ArrayPtr + canonical_function_types: u32, // ArrayPtr + canonical_record_types: u32, // ArrayPtr + canonical_type_parameters: u32, // ArrayPtr + canonical_type_arguments: u32, // ArrayPtr + async_library: u32, // LibraryPtr + core_library: u32, // LibraryPtr + _compact_hash_library: u32, // LibraryPtr + collection_library: u32, // LibraryPtr + concurrent_library: u32, // LibraryPtr + convert_library: u32, // LibraryPtr + developer_library: u32, // LibraryPtr + ffi_library: u32, // LibraryPtr + _internal_library: u32, // LibraryPtr + isolate_library: u32, // LibraryPtr + math_library: u32, // LibraryPtr + mirrors_library: u32, // LibraryPtr + native_wrappers_library: u32, // LibraryPtr + root_library: u32, // LibraryPtr + typed_data_library: u32, // LibraryPtr + _vm_library: u32, // LibraryPtr + _vmservice_library: u32, // LibraryPtr + native_assets_library: u32, // LibraryPtr + native_assets_map: u32, // ArrayPtr + libraries: u32, // GrowableObjectArrayPtr + libraries_map: u32, // ArrayPtr + uri_to_resolved_uri_map: u32, // ArrayPtr + resolved_uri_to_uri_map: u32, // ArrayPtr + last_libraries_count: u32, // SmiPtr + loading_units: u32, // ArrayPtr + closure_functions: u32, // GrowableObjectArrayPtr + closure_functions_table: u32, // ArrayPtr + pending_classes: u32, // GrowableObjectArrayPtr + record_field_names_map: u32, // ArrayPtr + record_field_names: u32, // ArrayPtr + stack_overflow: u32, // InstancePtr + out_of_memory: u32, // InstancePtr + growable_list_factory: u32, // FunctionPtr + simple_instance_of_function: u32, // FunctionPtr + simple_instance_of_true_function: u32, // FunctionPtr + simple_instance_of_false_function: u32, // FunctionPtr + async_star_stream_controller_add: u32, // FunctionPtr + async_star_stream_controller_add_stream: u32, // FunctionPtr + suspend_state_init_async: u32, // FunctionPtr + suspend_state_await: u32, // FunctionPtr + suspend_state_await_with_type_check: u32, // FunctionPtr + suspend_state_return_async: u32, // FunctionPtr + suspend_state_return_async_not_future: u32, // FunctionPtr + suspend_state_init_async_star: u32, // FunctionPtr + suspend_state_yield_async_star: u32, // FunctionPtr + suspend_state_return_async_star: u32, // FunctionPtr + suspend_state_init_sync_star: u32, // FunctionPtr + suspend_state_suspend_sync_star_at_start: u32, // FunctionPtr + suspend_state_handle_exception: u32, // FunctionPtr + async_star_stream_controller: u32, // ClassPtr + stream_class: u32, // ClassPtr + sync_star_iterator_class: u32, // ClassPtr + async_star_stream_controller_async_star_body: u32, // FieldPtr + sync_star_iterator_current: u32, // FieldPtr + sync_star_iterator_state: u32, // FieldPtr + sync_star_iterator_yield_star_iterable: u32, // FieldPtr + canonicalized_stack_map_entries: u32, // CompressedStackMapsPtr + global_object_pool: u32, // ObjectPoolPtr + unique_dynamic_targets: u32, // ArrayPtr + megamorphic_cache_table: u32, // GrowableObjectArrayPtr + ffi_callback_code: u32, // GrowableObjectArrayPtr + dispatch_table_null_error_stub: u32, // CodePtr + late_initialization_error_stub_with_fpu_regs_stub: u32, // CodePtr + late_initialization_error_stub_without_fpu_regs_stub: u32, // CodePtr + null_error_stub_with_fpu_regs_stub: u32, // CodePtr + null_error_stub_without_fpu_regs_stub: u32, // CodePtr + null_arg_error_stub_with_fpu_regs_stub: u32, // CodePtr + null_arg_error_stub_without_fpu_regs_stub: u32, // CodePtr + null_cast_error_stub_with_fpu_regs_stub: u32, // CodePtr + null_cast_error_stub_without_fpu_regs_stub: u32, // CodePtr + range_error_stub_with_fpu_regs_stub: u32, // CodePtr + range_error_stub_without_fpu_regs_stub: u32, // CodePtr + write_error_stub_with_fpu_regs_stub: u32, // CodePtr + write_error_stub_without_fpu_regs_stub: u32, // CodePtr + field_access_error_stub_with_fpu_regs_stub: u32, // CodePtr + field_access_error_stub_without_fpu_regs_stub: u32, // CodePtr + allocate_mint_with_fpu_regs_stub: u32, // CodePtr + allocate_mint_without_fpu_regs_stub: u32, // CodePtr + stack_overflow_stub_with_fpu_regs_stub: u32, // CodePtr + stack_overflow_stub_without_fpu_regs_stub: u32, // CodePtr + allocate_array_stub: u32, // CodePtr + allocate_mint_stub: u32, // CodePtr + allocate_double_stub: u32, // CodePtr + allocate_float32x4_stub: u32, // CodePtr + allocate_float64x2_stub: u32, // CodePtr + allocate_int32x4_stub: u32, // CodePtr + allocate_int8_array_stub: u32, // CodePtr + allocate_uint8_array_stub: u32, // CodePtr + allocate_uint8_clamped_array_stub: u32, // CodePtr + allocate_int16_array_stub: u32, // CodePtr + allocate_uint16_array_stub: u32, // CodePtr + allocate_int32_array_stub: u32, // CodePtr + allocate_uint32_array_stub: u32, // CodePtr + allocate_int64_array_stub: u32, // CodePtr + allocate_uint64_array_stub: u32, // CodePtr + allocate_float32_array_stub: u32, // CodePtr + allocate_float64_array_stub: u32, // CodePtr + allocate_float32x4_array_stub: u32, // CodePtr + allocate_int32x4_array_stub: u32, // CodePtr + allocate_float64x2_array_stub: u32, // CodePtr + allocate_closure_stub: u32, // CodePtr + allocate_closure_generic_stub: u32, // CodePtr + allocate_closure_ta_stub: u32, // CodePtr + allocate_closure_ta_generic_stub: u32, // CodePtr + allocate_context_stub: u32, // CodePtr + allocate_growable_array_stub: u32, // CodePtr + allocate_object_stub: u32, // CodePtr + allocate_object_parametrized_stub: u32, // CodePtr + allocate_record_stub: u32, // CodePtr + allocate_record2_stub: u32, // CodePtr + allocate_record2_named_stub: u32, // CodePtr + allocate_record3_stub: u32, // CodePtr + allocate_record3_named_stub: u32, // CodePtr + allocate_unhandled_exception_stub: u32, // CodePtr + check_isolate_field_access_stub: u32, // CodePtr + clone_context_stub: u32, // CodePtr + write_barrier_wrappers_stub: u32, // CodePtr + array_write_barrier_stub: u32, // CodePtr + throw_stub: u32, // CodePtr + re_throw_stub: u32, // CodePtr + instance_of_stub: u32, // CodePtr + init_static_field_stub: u32, // CodePtr + init_late_static_field_stub: u32, // CodePtr + init_late_final_static_field_stub: u32, // CodePtr + init_instance_field_stub: u32, // CodePtr + init_late_instance_field_stub: u32, // CodePtr + init_late_final_instance_field_stub: u32, // CodePtr + init_shared_late_static_field_stub: u32, // CodePtr + call_closure_no_such_method_stub: u32, // CodePtr + default_tts_stub: u32, // CodePtr + default_nullable_tts_stub: u32, // CodePtr + top_type_tts_stub: u32, // CodePtr + nullable_type_parameter_tts_stub: u32, // CodePtr + type_parameter_tts_stub: u32, // CodePtr + unreachable_tts_stub: u32, // CodePtr + ffi_callback_functions: u32, // ArrayPtr + resume_stub: u32, // CodePtr + slow_tts_stub: u32, // CodePtr + await_stub: u32, // CodePtr + await_with_type_check_stub: u32, // CodePtr + clone_suspend_state_stub: u32, // CodePtr + ffi_async_callback_send_stub: u32, // CodePtr + init_async_stub: u32, // CodePtr + return_async_stub: u32, // CodePtr + return_async_not_future_stub: u32, // CodePtr + init_async_star_stub: u32, // CodePtr + yield_async_star_stub: u32, // CodePtr + return_async_star_stub: u32, // CodePtr + init_sync_star_stub: u32, // CodePtr + suspend_sync_star_at_start_stub: u32, // CodePtr + suspend_sync_star_at_yield_stub: u32, // CodePtr + dispatch_table_code_entries: u32, // ArrayPtr + instructions_tables: u32, // GrowableObjectArrayPtr + obfuscation_map: u32, // ArrayPtr + loading_unit_uris: u32, // ArrayPtr + ffi_pointer_class: u32, // ClassPtr + ffi_native_type_class: u32, // ClassPtr +} +pub struct IsolateObjectStore +// small one, not too important +{ + dart_args_1: u32, // ArrayPtr + dart_args_2: u32, // ArrayPtr + resume_capabilities: u32, // GrowableObjectArrayPtr + exit_listeners: u32, // GrowableObjectArrayPtr + error_listeners: u32, // GrowableObjectArrayPtr +} + +pub fn resolve_object_store() -> anyhow::Result<()> { + Ok(()) +} diff --git a/crates/flutterdec-serwalker/src/snapshot.rs b/crates/flutterdec-serwalker/src/snapshot.rs index d55f676..6b4e999 100644 --- a/crates/flutterdec-serwalker/src/snapshot.rs +++ b/crates/flutterdec-serwalker/src/snapshot.rs @@ -38,6 +38,7 @@ impl TryFrom for SnapshotKind { pub struct DataSnapshot { clusters: HashMap>, cluster_order: Vec, // used in the fill step to know which cluster's read_fill function to call + roots: ProgramRoots, magic_bytes: u32, size: u64, @@ -92,7 +93,7 @@ impl DataSnapshot { } fn parse_clusters(&mut self, stream: &mut Stream) -> anyhow::Result<()> { - let mut curr_ref_id: u64 = self.num_base_objects + 1; // all objects are numbered starting from 0 + let mut curr_ref_id: u64 = self.num_base_objects + 1; // all objects are numbered starting from num_base_objects + 1 self.start_of_alloc_area = stream.get_current_pos(); for _cluster_idx in 0..self.num_clusters { @@ -106,7 +107,7 @@ impl DataSnapshot { cluster.read_alloc(&mut curr_ref_id, stream)?; - // Composite key exactly as suggested by PR reviewer + // Composite key exactly as suggested to PR reviewer let key = (cid as u32) << 2 | ((decoded_tags.is_canonical() as u32) << 1) | (decoded_tags.is_deeply_immutable() as u32); @@ -124,7 +125,8 @@ impl DataSnapshot { Ok(()) } - fn parse_roots(&mut self, _stream: &mut Stream) -> anyhow::Result<()> { + fn parse_roots(&mut self, stream: &mut Stream) -> anyhow::Result<()> { + // to-do (parse object store and field tables) Ok(()) } } diff --git a/crates/flutterdec-serwalker/src/stream.rs b/crates/flutterdec-serwalker/src/stream.rs index 8d4893c..8e8a8ff 100644 --- a/crates/flutterdec-serwalker/src/stream.rs +++ b/crates/flutterdec-serwalker/src/stream.rs @@ -2,13 +2,13 @@ use anyhow::{anyhow, bail, Result}; use crate::constants::{DATA_BITS_PER_BYTE, SIGNED_M, UNSIGNED_M, UNSIGNED_MAX_DATA_PER_BYTE}; -pub struct Stream<'a> { - byte_stream: &'a [u8], +pub struct Stream { + byte_stream: &[u8], curr_stream_offset: usize, } -impl<'a> Stream<'a> { - pub fn new(byte_stream: &'a [u8]) -> Self { +impl Stream { + pub fn new(byte_stream: &[u8]) -> Self { Self { byte_stream, curr_stream_offset: 0, From ebfe471851081ea0004b8ffc8eca28520bd005a3 Mon Sep 17 00:00:00 2001 From: kalixtez Date: Tue, 28 Jul 2026 03:12:29 -0500 Subject: [PATCH 44/50] feat(serwalker): added multiple submodules to the info_producer module --- .../flutterdec-serwalker/src/info_producer/classes_info.rs | 1 + .../src/info_producer/functions_info.rs | 1 + .../src/info_producer/libraries_info.rs | 1 + crates/flutterdec-serwalker/src/info_producer/mod.rs | 5 +++++ .../src/info_producer/object_pool_info.rs | 1 + crates/flutterdec-serwalker/src/object_store/mod.rs | 2 ++ .../src/object_store/special_resolvers.rs | 7 +++++++ crates/flutterdec-serwalker/src/raw_object/mod.rs | 2 +- 8 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 crates/flutterdec-serwalker/src/info_producer/classes_info.rs create mode 100644 crates/flutterdec-serwalker/src/info_producer/functions_info.rs create mode 100644 crates/flutterdec-serwalker/src/info_producer/libraries_info.rs create mode 100644 crates/flutterdec-serwalker/src/info_producer/object_pool_info.rs create mode 100644 crates/flutterdec-serwalker/src/object_store/special_resolvers.rs diff --git a/crates/flutterdec-serwalker/src/info_producer/classes_info.rs b/crates/flutterdec-serwalker/src/info_producer/classes_info.rs new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/crates/flutterdec-serwalker/src/info_producer/classes_info.rs @@ -0,0 +1 @@ + diff --git a/crates/flutterdec-serwalker/src/info_producer/functions_info.rs b/crates/flutterdec-serwalker/src/info_producer/functions_info.rs new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/crates/flutterdec-serwalker/src/info_producer/functions_info.rs @@ -0,0 +1 @@ + diff --git a/crates/flutterdec-serwalker/src/info_producer/libraries_info.rs b/crates/flutterdec-serwalker/src/info_producer/libraries_info.rs new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/crates/flutterdec-serwalker/src/info_producer/libraries_info.rs @@ -0,0 +1 @@ + diff --git a/crates/flutterdec-serwalker/src/info_producer/mod.rs b/crates/flutterdec-serwalker/src/info_producer/mod.rs index c3761f0..d4ff462 100644 --- a/crates/flutterdec-serwalker/src/info_producer/mod.rs +++ b/crates/flutterdec-serwalker/src/info_producer/mod.rs @@ -1,3 +1,8 @@ +mod classes_info; +mod functions_info; +mod libraries_info; +mod object_pool_info; + use anyhow::Error; use flutterdec_adapter::ProgramModel; diff --git a/crates/flutterdec-serwalker/src/info_producer/object_pool_info.rs b/crates/flutterdec-serwalker/src/info_producer/object_pool_info.rs new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/crates/flutterdec-serwalker/src/info_producer/object_pool_info.rs @@ -0,0 +1 @@ + diff --git a/crates/flutterdec-serwalker/src/object_store/mod.rs b/crates/flutterdec-serwalker/src/object_store/mod.rs index 5e5176f..5a0aa15 100644 --- a/crates/flutterdec-serwalker/src/object_store/mod.rs +++ b/crates/flutterdec-serwalker/src/object_store/mod.rs @@ -1,3 +1,5 @@ +mod special_resolvers; + struct ObjectStore { list_class: u32, // ClassPtr map_class: u32, // ClassPtr diff --git a/crates/flutterdec-serwalker/src/object_store/special_resolvers.rs b/crates/flutterdec-serwalker/src/object_store/special_resolvers.rs new file mode 100644 index 0000000..16e7d43 --- /dev/null +++ b/crates/flutterdec-serwalker/src/object_store/special_resolvers.rs @@ -0,0 +1,7 @@ +use flutterdec_adapter::LibraryInfo; + +pub fn resolve_root_library() -> anyhow::Result { + let root_lib: LibraryInfo; + + Ok(root_lib) +} diff --git a/crates/flutterdec-serwalker/src/raw_object/mod.rs b/crates/flutterdec-serwalker/src/raw_object/mod.rs index 7afb310..3fd3d5e 100644 --- a/crates/flutterdec-serwalker/src/raw_object/mod.rs +++ b/crates/flutterdec-serwalker/src/raw_object/mod.rs @@ -196,7 +196,7 @@ pub struct UnlinkedCall { #[derive(Default)] pub struct ObjectPool { - // Fieldless class + data: Vec, // vector holding the array of reference ids making up the object pool } #[derive(Default)] From ab6aef0b66ba778e323546e35cf53b3fc006b864 Mon Sep 17 00:00:00 2001 From: kalixtez Date: Wed, 29 Jul 2026 01:44:28 -0500 Subject: [PATCH 45/50] chore(serwalker): modified module structure, renamed object_store module for program_roots, renamed the adapter interface function in lib.rs and info producer functions to better reflect the purpose of this feature --- .../src/info_producer/mod.rs | 6 ++-- crates/flutterdec-serwalker/src/lib.rs | 14 +++++----- .../src/object_store/special_resolvers.rs | 7 ----- .../src/program_roots/mod.rs | 28 +++++++++++++++++++ .../mod.rs => program_roots/structs.rs} | 18 ++++++++---- crates/flutterdec-serwalker/src/snapshot.rs | 1 + 6 files changed, 52 insertions(+), 22 deletions(-) delete mode 100644 crates/flutterdec-serwalker/src/object_store/special_resolvers.rs create mode 100644 crates/flutterdec-serwalker/src/program_roots/mod.rs rename crates/flutterdec-serwalker/src/{object_store/mod.rs => program_roots/structs.rs} (98%) diff --git a/crates/flutterdec-serwalker/src/info_producer/mod.rs b/crates/flutterdec-serwalker/src/info_producer/mod.rs index d4ff462..4761c24 100644 --- a/crates/flutterdec-serwalker/src/info_producer/mod.rs +++ b/crates/flutterdec-serwalker/src/info_producer/mod.rs @@ -8,21 +8,21 @@ use flutterdec_adapter::ProgramModel; use crate::snapshot::DataSnapshot; -pub fn enrich_model_headers( +pub fn produce_model_headers( model: &mut ProgramModel, snapshot: &DataSnapshot, ) -> anyhow::Result<()> { Ok(()) } -pub fn enrich_model_object_info( +pub fn produce_model_object_info( model: &mut ProgramModel, snapshot: &DataSnapshot, ) -> anyhow::Result<()> { Ok(()) } -pub fn enrich_model_object_pool( +pub fn produce_model_object_pool( model: &mut ProgramModel, snapshot: &DataSnapshot, ) -> anyhow::Result<()> { diff --git a/crates/flutterdec-serwalker/src/lib.rs b/crates/flutterdec-serwalker/src/lib.rs index cb37d28..b27baea 100644 --- a/crates/flutterdec-serwalker/src/lib.rs +++ b/crates/flutterdec-serwalker/src/lib.rs @@ -2,22 +2,22 @@ mod constants; mod utils; mod cluster; -mod object_store; +mod program_roots; mod raw_object; mod snapshot; mod info_producer; mod stream; -use flutterdec_adapter::ProgramModel; +use flutterdec_adapter::{AdapterInput, ProgramModel}; use crate::{ - info_producer::{enrich_model_headers, enrich_model_object_info}, + info_producer::{produce_model_headers, produce_model_object_info}, snapshot::parse_snapshot, stream::Stream, }; -fn walk_snapshot_and_enrich_model( +fn walk_snapshot_and_produce_model( isolate_data: &[u8], isolate_instr: &[u8], vm_data: Option<&[u8]>, @@ -28,9 +28,9 @@ fn walk_snapshot_and_enrich_model( let mut isolate_data_stream = Stream::new(isolate_data); let isolate_data_snapshot = parse_snapshot(&mut isolate_data_stream)?; - enrich_model_headers(&mut program_model, &isolate_data_snapshot); - enrich_model_object_info(&mut program_model, &isolate_data_snapshot); - enrich_model_object_pool(&mut program_model, &isolate_data_snapshot); + produce_model_headers(&mut program_model, &isolate_data_snapshot); + produce_model_object_info(&mut program_model, &isolate_data_snapshot); + produce_model_object_pool(&mut program_model, &isolate_data_snapshot); program_model } diff --git a/crates/flutterdec-serwalker/src/object_store/special_resolvers.rs b/crates/flutterdec-serwalker/src/object_store/special_resolvers.rs deleted file mode 100644 index 16e7d43..0000000 --- a/crates/flutterdec-serwalker/src/object_store/special_resolvers.rs +++ /dev/null @@ -1,7 +0,0 @@ -use flutterdec_adapter::LibraryInfo; - -pub fn resolve_root_library() -> anyhow::Result { - let root_lib: LibraryInfo; - - Ok(root_lib) -} diff --git a/crates/flutterdec-serwalker/src/program_roots/mod.rs b/crates/flutterdec-serwalker/src/program_roots/mod.rs new file mode 100644 index 0000000..19805e3 --- /dev/null +++ b/crates/flutterdec-serwalker/src/program_roots/mod.rs @@ -0,0 +1,28 @@ +pub mod structs; + +pub fn parse_object_store() -> anyhow::Result { + let object_store: ObjectStore; + + Ok(object_store) +} + +pub fn parse_field_table() -> anyhow::Result<()> { + Ok(()) +} + +pub fn parse_dispatch_table() -> anyhow::Result<()> { + Ok(()) +} + +use flutterdec_adapter::LibraryInfo; + +use crate::snapshot::DataSnapshot; + +pub fn resolve_root_library( + root_lib_ref: u32, + data_snapshot: &DataSnapshot, +) -> anyhow::Result { + let root_lib: LibraryInfo; + + Ok(root_lib) +} diff --git a/crates/flutterdec-serwalker/src/object_store/mod.rs b/crates/flutterdec-serwalker/src/program_roots/structs.rs similarity index 98% rename from crates/flutterdec-serwalker/src/object_store/mod.rs rename to crates/flutterdec-serwalker/src/program_roots/structs.rs index 5a0aa15..c611e2b 100644 --- a/crates/flutterdec-serwalker/src/object_store/mod.rs +++ b/crates/flutterdec-serwalker/src/program_roots/structs.rs @@ -1,5 +1,3 @@ -mod special_resolvers; - struct ObjectStore { list_class: u32, // ClassPtr map_class: u32, // ClassPtr @@ -264,7 +262,7 @@ struct ObjectStore { ffi_native_type_class: u32, // ClassPtr } -pub struct IsolateObjectStore +struct IsolateObjectStore // small one, not too important { dart_args_1: u32, // ArrayPtr @@ -274,6 +272,16 @@ pub struct IsolateObjectStore error_listeners: u32, // GrowableObjectArrayPtr } -pub fn resolve_object_store() -> anyhow::Result<()> { - Ok(()) +struct FieldTable { + // to-do +} + +struct DispatchTable { + // to-do +} + +pub struct ProgramRoots { + object_store: ObjectStore, + field_table: FieldTable, + dispatch_table: DispatchTable, } diff --git a/crates/flutterdec-serwalker/src/snapshot.rs b/crates/flutterdec-serwalker/src/snapshot.rs index 2d3d25a..497753c 100644 --- a/crates/flutterdec-serwalker/src/snapshot.rs +++ b/crates/flutterdec-serwalker/src/snapshot.rs @@ -2,6 +2,7 @@ use std::collections::HashMap; use crate::cluster::{decide_cluster, Cluster}; use crate::constants::{self, ClassId, MAGIC_BYTES, UNSIGNED_M}; +use crate::program_roots::structs::ProgramRoots; use crate::stream::Stream; use crate::utils::{decode_tags, DecodedTags}; From 7b652c714362dc97214efc76f3e80410f4c485fc Mon Sep 17 00:00:00 2001 From: kalixtez Date: Wed, 29 Jul 2026 01:50:38 -0500 Subject: [PATCH 46/50] chore(serwalker): made lib.rs adapter interface function public and changed its return type for an anyhow Result. Its parameters were also modified, changing the 4 explicit streams for a wrapper AdapterInput structure reference, already pointing at said streams --- crates/flutterdec-serwalker/src/lib.rs | 13 +++++-------- .../src/program_roots/structs.rs | 2 +- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/crates/flutterdec-serwalker/src/lib.rs b/crates/flutterdec-serwalker/src/lib.rs index b27baea..a14977f 100644 --- a/crates/flutterdec-serwalker/src/lib.rs +++ b/crates/flutterdec-serwalker/src/lib.rs @@ -17,20 +17,17 @@ use crate::{ stream::Stream, }; -fn walk_snapshot_and_produce_model( - isolate_data: &[u8], - isolate_instr: &[u8], - vm_data: Option<&[u8]>, - vm_instr: Option<&[u8]>, -) -> ProgramModel { +pub fn walk_snapshot_and_produce_model( + adapter_input: &AdapterInput +) -> anyhow::Result { let program_model: ProgramModel; - let mut isolate_data_stream = Stream::new(isolate_data); + let mut isolate_data_stream = Stream::new(adapter_input.isolate_data); let isolate_data_snapshot = parse_snapshot(&mut isolate_data_stream)?; produce_model_headers(&mut program_model, &isolate_data_snapshot); produce_model_object_info(&mut program_model, &isolate_data_snapshot); produce_model_object_pool(&mut program_model, &isolate_data_snapshot); - program_model + Ok(program_model) } diff --git a/crates/flutterdec-serwalker/src/program_roots/structs.rs b/crates/flutterdec-serwalker/src/program_roots/structs.rs index c611e2b..440a077 100644 --- a/crates/flutterdec-serwalker/src/program_roots/structs.rs +++ b/crates/flutterdec-serwalker/src/program_roots/structs.rs @@ -1,4 +1,4 @@ -struct ObjectStore { +struct ObjectStore { // Layout for the 3.12 Dart version. list_class: u32, // ClassPtr map_class: u32, // ClassPtr set_class: u32, // ClassPtr From c8d2fa2a7358b267cabed55b5ef5f1dab3fce52e Mon Sep 17 00:00:00 2001 From: kalixtez Date: Wed, 29 Jul 2026 01:52:21 -0500 Subject: [PATCH 47/50] style(serwalker): ran cargo fmt --all --- crates/flutterdec-serwalker/src/lib.rs | 2 +- crates/flutterdec-serwalker/src/program_roots/structs.rs | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/flutterdec-serwalker/src/lib.rs b/crates/flutterdec-serwalker/src/lib.rs index a14977f..e4edc42 100644 --- a/crates/flutterdec-serwalker/src/lib.rs +++ b/crates/flutterdec-serwalker/src/lib.rs @@ -18,7 +18,7 @@ use crate::{ }; pub fn walk_snapshot_and_produce_model( - adapter_input: &AdapterInput + adapter_input: &AdapterInput, ) -> anyhow::Result { let program_model: ProgramModel; diff --git a/crates/flutterdec-serwalker/src/program_roots/structs.rs b/crates/flutterdec-serwalker/src/program_roots/structs.rs index 440a077..c0a4823 100644 --- a/crates/flutterdec-serwalker/src/program_roots/structs.rs +++ b/crates/flutterdec-serwalker/src/program_roots/structs.rs @@ -1,4 +1,5 @@ -struct ObjectStore { // Layout for the 3.12 Dart version. +struct ObjectStore { + // Layout for the 3.12 Dart version. list_class: u32, // ClassPtr map_class: u32, // ClassPtr set_class: u32, // ClassPtr From 027ed79258f24f4adadf1e629ef7027672c2792c Mon Sep 17 00:00:00 2001 From: kalixtez Date: Wed, 29 Jul 2026 23:06:56 -0500 Subject: [PATCH 48/50] feat(serwalkre): modified the ObjectStore declaration so it uses a macro to both declare the struct fields, and declare a static ObjectStore::read method that reads every ref_id. This was done so we don't have to use unsafe rust to mimic from and to_snapshot pointer arithmetic. Additionally, used repr(C) so the layout isnt changed if ever needed. Removed ObjectStore fields not present in FullAOT snapshots, and implemented the parse_roots method --- .../src/program_roots/mod.rs | 108 +++- .../src/program_roots/structs.rs | 574 ++++++++++-------- crates/flutterdec-serwalker/src/snapshot.rs | 13 +- crates/flutterdec-serwalker/src/stream.rs | 4 +- 4 files changed, 411 insertions(+), 288 deletions(-) diff --git a/crates/flutterdec-serwalker/src/program_roots/mod.rs b/crates/flutterdec-serwalker/src/program_roots/mod.rs index 19805e3..de44c81 100644 --- a/crates/flutterdec-serwalker/src/program_roots/mod.rs +++ b/crates/flutterdec-serwalker/src/program_roots/mod.rs @@ -1,28 +1,104 @@ pub mod structs; +use structs::{DispatchTable, DispatchTableEntry, FieldTable, ObjectStore}; -pub fn parse_object_store() -> anyhow::Result { - let object_store: ObjectStore; +use crate::stream::Stream; - Ok(object_store) +pub fn parse_object_store(stream: &mut Stream) -> anyhow::Result { + ObjectStore::read(stream) } -pub fn parse_field_table() -> anyhow::Result<()> { - Ok(()) -} +pub fn parse_field_table(stream: &mut Stream) -> anyhow::Result { + let mut field_table = FieldTable::default(); + let table_length = stream.read_unsigned()?; + + // extremely unlikely but one never knows + field_table.length = table_length.try_into().map_err(|table_length: u64| { + anyhow::anyhow!( + "field table of length {} does not fit in usize", + table_length + ) + })?; -pub fn parse_dispatch_table() -> anyhow::Result<()> { - Ok(()) + // field_table.field_refs = Vec::with_capacity(table_length as usize); // passed the try_into above, safe to raw cast + + for _ in 0..table_length { + let refid = stream.read_ref_id()?; + field_table.field_refs.push(refid); + } + + Ok(field_table) } -use flutterdec_adapter::LibraryInfo; +pub fn parse_dispatch_table(stream: &mut Stream) -> anyhow::Result { + const RECENT_COUNT: usize = 1 << 6; + const RECENT_MASK: usize = RECENT_COUNT - 1; + const MAX_REPEAT: i64 = RECENT_COUNT as i64 - 1; + const RECENT_MIN: i64 = -MAX_REPEAT; + const INDEX_BASE: i64 = MAX_REPEAT + 1; + + let length: usize = stream.read_unsigned()?.try_into().map_err(|length: u64| { + anyhow::anyhow!("dispatch table of length {length} does not fit in usize") + })?; + + // for an empty table, the serializer writes only its length. + if length == 0 { + return Ok(DispatchTable::default()); + } + + let first_code_ref = stream + .read_unsigned()? + .try_into() + .map_err(|reference: u64| { + anyhow::anyhow!("first Code-cluster reference {reference} does not fit in u32") + })?; + + let mut table = DispatchTable { + first_code_ref: Some(first_code_ref), + // dont reserve from an untrusted claimed length before consuming + // entries, same thing as FieldTable parsing. + entries: Vec::new(), + }; + let mut previous = DispatchTableEntry::Invalid; + let mut recent: [Option; RECENT_COUNT] = [None; RECENT_COUNT]; + let mut recent_index = 0; + let mut repeat_remaining = 0usize; + + while table.entries.len() < length { + if repeat_remaining != 0 { + table.entries.push(previous); + repeat_remaining -= 1; + continue; + } -use crate::snapshot::DataSnapshot; + let encoded = stream.read()? as i64; + match encoded { + 0 => previous = DispatchTableEntry::Invalid, + RECENT_MIN..=-1 => { + let slot = (!encoded) as usize; + previous = recent[slot].ok_or_else(|| { + anyhow::anyhow!( + "dispatch table recent-entry reference {slot} appears before it is defined" + ) + })?; + } + 1..=MAX_REPEAT => { + repeat_remaining = (encoded - 1) as usize; + } + _ => { + let code_index = (encoded - INDEX_BASE) as u64; + previous = DispatchTableEntry::CodeIndex(code_index); + recent[recent_index] = Some(previous); + recent_index = (recent_index + 1) & RECENT_MASK; + } + } + table.entries.push(previous); + } -pub fn resolve_root_library( - root_lib_ref: u32, - data_snapshot: &DataSnapshot, -) -> anyhow::Result { - let root_lib: LibraryInfo; + if repeat_remaining != 0 { + anyhow::bail!( + "dispatch table repeat encoding exceeds its declared length by {repeat_remaining} entries" + ); + } - Ok(root_lib) + Ok(table) } diff --git a/crates/flutterdec-serwalker/src/program_roots/structs.rs b/crates/flutterdec-serwalker/src/program_roots/structs.rs index c0a4823..deb1e69 100644 --- a/crates/flutterdec-serwalker/src/program_roots/structs.rs +++ b/crates/flutterdec-serwalker/src/program_roots/structs.rs @@ -1,270 +1,279 @@ -struct ObjectStore { - // Layout for the 3.12 Dart version. - list_class: u32, // ClassPtr - map_class: u32, // ClassPtr - set_class: u32, // ClassPtr - non_nullable_list_rare_type: u32, // TypePtr - non_nullable_map_rare_type: u32, // TypePtr - enum_index_field: u32, // FieldPtr - enum_name_field: u32, // FieldPtr - _object_equals_function: u32, // FunctionPtr - _object_hash_code_function: u32, // FunctionPtr - _object_to_string_function: u32, // FunctionPtr - symbol_class: u32, // ClassPtr - symbol_name_field: u32, // FieldPtr - ffi_array_class: u32, // ClassPtr - ffi_compound_class: u32, // ClassPtr - ffi_struct_class: u32, // ClassPtr - ffi_union_class: u32, // ClassPtr - ffi_varargs_class: u32, // ClassPtr - compound_offset_in_bytes_field: u32, // FieldPtr - compound_typed_data_base_field: u32, // FieldPtr - ffi_resolver_function: u32, // FunctionPtr - handle_finalizer_message_function: u32, // FunctionPtr - handle_native_finalizer_message_function: u32, // FunctionPtr - non_nullable_future_never_type: u32, // TypePtr - nullable_future_null_type: u32, // TypePtr - send_port_class: u32, // ClassPtr - capability_class: u32, // ClassPtr - transferable_class: u32, // ClassPtr - lookup_port_handler: u32, // FunctionPtr - lookup_open_ports: u32, // FunctionPtr - handle_message_function: u32, // FunctionPtr - object_class: u32, // ClassPtr - object_type: u32, // TypePtr - non_nullable_object_type: u32, // TypePtr - nullable_object_type: u32, // TypePtr - null_class: u32, // ClassPtr - null_type: u32, // TypePtr - never_class: u32, // ClassPtr - never_type: u32, // TypePtr - function_type: u32, // TypePtr - type_type: u32, // TypePtr - closure_class: u32, // ClassPtr - record_class: u32, // ClassPtr - number_type: u32, // TypePtr - nullable_number_type: u32, // TypePtr - int_type: u32, // TypePtr - non_nullable_int_type: u32, // TypePtr - nullable_int_type: u32, // TypePtr - integer_implementation_class: u32, // ClassPtr - int64_type: u32, // TypePtr - smi_class: u32, // ClassPtr - smi_type: u32, // TypePtr - mint_class: u32, // ClassPtr - mint_type: u32, // TypePtr - double_class: u32, // ClassPtr - double_type: u32, // TypePtr - nullable_double_type: u32, // TypePtr - float32x4_type: u32, // TypePtr - int32x4_type: u32, // TypePtr - float64x2_type: u32, // TypePtr - string_type: u32, // TypePtr - type_argument_int: u32, // TypeArgumentsPtr - type_argument_double: u32, // TypeArgumentsPtr - type_argument_never: u32, // TypeArgumentsPtr - type_argument_string: u32, // TypeArgumentsPtr - type_argument_string_dynamic: u32, // TypeArgumentsPtr - type_argument_string_string: u32, // TypeArgumentsPtr - compiletime_error_class: u32, // ClassPtr - pragma_class: u32, // ClassPtr - pragma_name: u32, // FieldPtr - pragma_options: u32, // FieldPtr - future_class: u32, // ClassPtr - future_or_class: u32, // ClassPtr - one_byte_string_class: u32, // ClassPtr - two_byte_string_class: u32, // ClassPtr - bool_type: u32, // TypePtr - bool_class: u32, // ClassPtr - array_class: u32, // ClassPtr - array_type: u32, // TypePtr - immutable_array_class: u32, // ClassPtr - growable_object_array_class: u32, // ClassPtr - map_impl_class: u32, // ClassPtr - const_map_impl_class: u32, // ClassPtr - set_impl_class: u32, // ClassPtr - const_set_impl_class: u32, // ClassPtr - float32x4_class: u32, // ClassPtr - int32x4_class: u32, // ClassPtr - float64x2_class: u32, // ClassPtr - error_class: u32, // ClassPtr - expando_class: u32, // ClassPtr - iterable_class: u32, // ClassPtr - weak_property_class: u32, // ClassPtr - weak_reference_class: u32, // ClassPtr - finalizer_class: u32, // ClassPtr - finalizer_entry_class: u32, // ClassPtr - native_finalizer_class: u32, // ClassPtr - dart_condition_variable_class: u32, // ClassPtr - dart_mutex_class: u32, // ClassPtr - symbol_table: u32, // WeakArrayPtr - regexp_table: u32, // WeakArrayPtr - canonical_types: u32, // ArrayPtr - canonical_function_types: u32, // ArrayPtr - canonical_record_types: u32, // ArrayPtr - canonical_type_parameters: u32, // ArrayPtr - canonical_type_arguments: u32, // ArrayPtr - async_library: u32, // LibraryPtr - core_library: u32, // LibraryPtr - _compact_hash_library: u32, // LibraryPtr - collection_library: u32, // LibraryPtr - concurrent_library: u32, // LibraryPtr - convert_library: u32, // LibraryPtr - developer_library: u32, // LibraryPtr - ffi_library: u32, // LibraryPtr - _internal_library: u32, // LibraryPtr - isolate_library: u32, // LibraryPtr - math_library: u32, // LibraryPtr - mirrors_library: u32, // LibraryPtr - native_wrappers_library: u32, // LibraryPtr - root_library: u32, // LibraryPtr - typed_data_library: u32, // LibraryPtr - _vm_library: u32, // LibraryPtr - _vmservice_library: u32, // LibraryPtr - native_assets_library: u32, // LibraryPtr - native_assets_map: u32, // ArrayPtr - libraries: u32, // GrowableObjectArrayPtr - libraries_map: u32, // ArrayPtr - uri_to_resolved_uri_map: u32, // ArrayPtr - resolved_uri_to_uri_map: u32, // ArrayPtr - last_libraries_count: u32, // SmiPtr - loading_units: u32, // ArrayPtr - closure_functions: u32, // GrowableObjectArrayPtr - closure_functions_table: u32, // ArrayPtr - pending_classes: u32, // GrowableObjectArrayPtr - record_field_names_map: u32, // ArrayPtr - record_field_names: u32, // ArrayPtr - stack_overflow: u32, // InstancePtr - out_of_memory: u32, // InstancePtr - growable_list_factory: u32, // FunctionPtr - simple_instance_of_function: u32, // FunctionPtr - simple_instance_of_true_function: u32, // FunctionPtr - simple_instance_of_false_function: u32, // FunctionPtr - async_star_stream_controller_add: u32, // FunctionPtr - async_star_stream_controller_add_stream: u32, // FunctionPtr - suspend_state_init_async: u32, // FunctionPtr - suspend_state_await: u32, // FunctionPtr - suspend_state_await_with_type_check: u32, // FunctionPtr - suspend_state_return_async: u32, // FunctionPtr - suspend_state_return_async_not_future: u32, // FunctionPtr - suspend_state_init_async_star: u32, // FunctionPtr - suspend_state_yield_async_star: u32, // FunctionPtr - suspend_state_return_async_star: u32, // FunctionPtr - suspend_state_init_sync_star: u32, // FunctionPtr - suspend_state_suspend_sync_star_at_start: u32, // FunctionPtr - suspend_state_handle_exception: u32, // FunctionPtr - async_star_stream_controller: u32, // ClassPtr - stream_class: u32, // ClassPtr - sync_star_iterator_class: u32, // ClassPtr - async_star_stream_controller_async_star_body: u32, // FieldPtr - sync_star_iterator_current: u32, // FieldPtr - sync_star_iterator_state: u32, // FieldPtr - sync_star_iterator_yield_star_iterable: u32, // FieldPtr - canonicalized_stack_map_entries: u32, // CompressedStackMapsPtr - global_object_pool: u32, // ObjectPoolPtr - unique_dynamic_targets: u32, // ArrayPtr - megamorphic_cache_table: u32, // GrowableObjectArrayPtr - ffi_callback_code: u32, // GrowableObjectArrayPtr - dispatch_table_null_error_stub: u32, // CodePtr - late_initialization_error_stub_with_fpu_regs_stub: u32, // CodePtr - late_initialization_error_stub_without_fpu_regs_stub: u32, // CodePtr - null_error_stub_with_fpu_regs_stub: u32, // CodePtr - null_error_stub_without_fpu_regs_stub: u32, // CodePtr - null_arg_error_stub_with_fpu_regs_stub: u32, // CodePtr - null_arg_error_stub_without_fpu_regs_stub: u32, // CodePtr - null_cast_error_stub_with_fpu_regs_stub: u32, // CodePtr - null_cast_error_stub_without_fpu_regs_stub: u32, // CodePtr - range_error_stub_with_fpu_regs_stub: u32, // CodePtr - range_error_stub_without_fpu_regs_stub: u32, // CodePtr - write_error_stub_with_fpu_regs_stub: u32, // CodePtr - write_error_stub_without_fpu_regs_stub: u32, // CodePtr - field_access_error_stub_with_fpu_regs_stub: u32, // CodePtr - field_access_error_stub_without_fpu_regs_stub: u32, // CodePtr - allocate_mint_with_fpu_regs_stub: u32, // CodePtr - allocate_mint_without_fpu_regs_stub: u32, // CodePtr - stack_overflow_stub_with_fpu_regs_stub: u32, // CodePtr - stack_overflow_stub_without_fpu_regs_stub: u32, // CodePtr - allocate_array_stub: u32, // CodePtr - allocate_mint_stub: u32, // CodePtr - allocate_double_stub: u32, // CodePtr - allocate_float32x4_stub: u32, // CodePtr - allocate_float64x2_stub: u32, // CodePtr - allocate_int32x4_stub: u32, // CodePtr - allocate_int8_array_stub: u32, // CodePtr - allocate_uint8_array_stub: u32, // CodePtr - allocate_uint8_clamped_array_stub: u32, // CodePtr - allocate_int16_array_stub: u32, // CodePtr - allocate_uint16_array_stub: u32, // CodePtr - allocate_int32_array_stub: u32, // CodePtr - allocate_uint32_array_stub: u32, // CodePtr - allocate_int64_array_stub: u32, // CodePtr - allocate_uint64_array_stub: u32, // CodePtr - allocate_float32_array_stub: u32, // CodePtr - allocate_float64_array_stub: u32, // CodePtr - allocate_float32x4_array_stub: u32, // CodePtr - allocate_int32x4_array_stub: u32, // CodePtr - allocate_float64x2_array_stub: u32, // CodePtr - allocate_closure_stub: u32, // CodePtr - allocate_closure_generic_stub: u32, // CodePtr - allocate_closure_ta_stub: u32, // CodePtr - allocate_closure_ta_generic_stub: u32, // CodePtr - allocate_context_stub: u32, // CodePtr - allocate_growable_array_stub: u32, // CodePtr - allocate_object_stub: u32, // CodePtr - allocate_object_parametrized_stub: u32, // CodePtr - allocate_record_stub: u32, // CodePtr - allocate_record2_stub: u32, // CodePtr - allocate_record2_named_stub: u32, // CodePtr - allocate_record3_stub: u32, // CodePtr - allocate_record3_named_stub: u32, // CodePtr - allocate_unhandled_exception_stub: u32, // CodePtr - check_isolate_field_access_stub: u32, // CodePtr - clone_context_stub: u32, // CodePtr - write_barrier_wrappers_stub: u32, // CodePtr - array_write_barrier_stub: u32, // CodePtr - throw_stub: u32, // CodePtr - re_throw_stub: u32, // CodePtr - instance_of_stub: u32, // CodePtr - init_static_field_stub: u32, // CodePtr - init_late_static_field_stub: u32, // CodePtr - init_late_final_static_field_stub: u32, // CodePtr - init_instance_field_stub: u32, // CodePtr - init_late_instance_field_stub: u32, // CodePtr - init_late_final_instance_field_stub: u32, // CodePtr - init_shared_late_static_field_stub: u32, // CodePtr - call_closure_no_such_method_stub: u32, // CodePtr - default_tts_stub: u32, // CodePtr - default_nullable_tts_stub: u32, // CodePtr - top_type_tts_stub: u32, // CodePtr - nullable_type_parameter_tts_stub: u32, // CodePtr - type_parameter_tts_stub: u32, // CodePtr - unreachable_tts_stub: u32, // CodePtr - ffi_callback_functions: u32, // ArrayPtr - resume_stub: u32, // CodePtr - slow_tts_stub: u32, // CodePtr - await_stub: u32, // CodePtr - await_with_type_check_stub: u32, // CodePtr - clone_suspend_state_stub: u32, // CodePtr - ffi_async_callback_send_stub: u32, // CodePtr - init_async_stub: u32, // CodePtr - return_async_stub: u32, // CodePtr - return_async_not_future_stub: u32, // CodePtr - init_async_star_stub: u32, // CodePtr - yield_async_star_stub: u32, // CodePtr - return_async_star_stub: u32, // CodePtr - init_sync_star_stub: u32, // CodePtr - suspend_sync_star_at_start_stub: u32, // CodePtr - suspend_sync_star_at_yield_stub: u32, // CodePtr - dispatch_table_code_entries: u32, // ArrayPtr - instructions_tables: u32, // GrowableObjectArrayPtr - obfuscation_map: u32, // ArrayPtr - loading_unit_uris: u32, // ArrayPtr - ffi_pointer_class: u32, // ClassPtr - ffi_native_type_class: u32, // ClassPtr +use crate::stream::Stream; + +macro_rules! object_store_aot_fields { + ($emit:ident) => { + $emit! { + // Layout for the 3.12 Dart version, and as early as 3.11 + list_class: u32, // ClassPtr + map_class: u32, // ClassPtr + set_class: u32, // ClassPtr + non_nullable_list_rare_type: u32, // TypePtr + non_nullable_map_rare_type: u32, // TypePtr + enum_index_field: u32, // FieldPtr + enum_name_field: u32, // FieldPtr + _object_equals_function: u32, // FunctionPtr + _object_hash_code_function: u32, // FunctionPtr + _object_to_string_function: u32, // FunctionPtr + symbol_class: u32, // ClassPtr + symbol_name_field: u32, // FieldPtr + ffi_array_class: u32, // ClassPtr + ffi_compound_class: u32, // ClassPtr + ffi_struct_class: u32, // ClassPtr + ffi_union_class: u32, // ClassPtr + ffi_varargs_class: u32, // ClassPtr + compound_offset_in_bytes_field: u32, // FieldPtr + compound_typed_data_base_field: u32, // FieldPtr + ffi_resolver_function: u32, // FunctionPtr + handle_finalizer_message_function: u32, // FunctionPtr + handle_native_finalizer_message_function: u32, // FunctionPtr + non_nullable_future_never_type: u32, // TypePtr + nullable_future_null_type: u32, // TypePtr + send_port_class: u32, // ClassPtr + capability_class: u32, // ClassPtr + transferable_class: u32, // ClassPtr + lookup_port_handler: u32, // FunctionPtr + lookup_open_ports: u32, // FunctionPtr + handle_message_function: u32, // FunctionPtr + object_class: u32, // ClassPtr + object_type: u32, // TypePtr + non_nullable_object_type: u32, // TypePtr + nullable_object_type: u32, // TypePtr + null_class: u32, // ClassPtr + null_type: u32, // TypePtr + never_class: u32, // ClassPtr + never_type: u32, // TypePtr + function_type: u32, // TypePtr + type_type: u32, // TypePtr + closure_class: u32, // ClassPtr + record_class: u32, // ClassPtr + number_type: u32, // TypePtr + nullable_number_type: u32, // TypePtr + int_type: u32, // TypePtr + non_nullable_int_type: u32, // TypePtr + nullable_int_type: u32, // TypePtr + integer_implementation_class: u32, // ClassPtr + int64_type: u32, // TypePtr + smi_class: u32, // ClassPtr + smi_type: u32, // TypePtr + mint_class: u32, // ClassPtr + mint_type: u32, // TypePtr + double_class: u32, // ClassPtr + double_type: u32, // TypePtr + nullable_double_type: u32, // TypePtr + float32x4_type: u32, // TypePtr + int32x4_type: u32, // TypePtr + float64x2_type: u32, // TypePtr + string_type: u32, // TypePtr + type_argument_int: u32, // TypeArgumentsPtr + type_argument_double: u32, // TypeArgumentsPtr + type_argument_never: u32, // TypeArgumentsPtr + type_argument_string: u32, // TypeArgumentsPtr + type_argument_string_dynamic: u32, // TypeArgumentsPtr + type_argument_string_string: u32, // TypeArgumentsPtr + compiletime_error_class: u32, // ClassPtr + pragma_class: u32, // ClassPtr + pragma_name: u32, // FieldPtr + pragma_options: u32, // FieldPtr + future_class: u32, // ClassPtr + future_or_class: u32, // ClassPtr + one_byte_string_class: u32, // ClassPtr + two_byte_string_class: u32, // ClassPtr + bool_type: u32, // TypePtr + bool_class: u32, // ClassPtr + array_class: u32, // ClassPtr + array_type: u32, // TypePtr + immutable_array_class: u32, // ClassPtr + growable_object_array_class: u32, // ClassPtr + map_impl_class: u32, // ClassPtr + const_map_impl_class: u32, // ClassPtr + set_impl_class: u32, // ClassPtr + const_set_impl_class: u32, // ClassPtr + float32x4_class: u32, // ClassPtr + int32x4_class: u32, // ClassPtr + float64x2_class: u32, // ClassPtr + error_class: u32, // ClassPtr + expando_class: u32, // ClassPtr + iterable_class: u32, // ClassPtr + weak_property_class: u32, // ClassPtr + weak_reference_class: u32, // ClassPtr + finalizer_class: u32, // ClassPtr + finalizer_entry_class: u32, // ClassPtr + native_finalizer_class: u32, // ClassPtr + dart_condition_variable_class: u32, // ClassPtr + dart_mutex_class: u32, // ClassPtr + symbol_table: u32, // WeakArrayPtr + regexp_table: u32, // WeakArrayPtr + canonical_types: u32, // ArrayPtr + canonical_function_types: u32, // ArrayPtr + canonical_record_types: u32, // ArrayPtr + canonical_type_parameters: u32, // ArrayPtr + canonical_type_arguments: u32, // ArrayPtr + async_library: u32, // LibraryPtr + core_library: u32, // LibraryPtr + _compact_hash_library: u32, // LibraryPtr + collection_library: u32, // LibraryPtr + concurrent_library: u32, // LibraryPtr + convert_library: u32, // LibraryPtr + developer_library: u32, // LibraryPtr + ffi_library: u32, // LibraryPtr + _internal_library: u32, // LibraryPtr + isolate_library: u32, // LibraryPtr + math_library: u32, // LibraryPtr + mirrors_library: u32, // LibraryPtr + native_wrappers_library: u32, // LibraryPtr + root_library: u32, // LibraryPtr + typed_data_library: u32, // LibraryPtr + _vm_library: u32, // LibraryPtr + _vmservice_library: u32, // LibraryPtr + native_assets_library: u32, // LibraryPtr + native_assets_map: u32, // ArrayPtr + libraries: u32, // GrowableObjectArrayPtr + libraries_map: u32, // ArrayPtr + uri_to_resolved_uri_map: u32, // ArrayPtr + resolved_uri_to_uri_map: u32, // ArrayPtr + last_libraries_count: u32, // SmiPtr + loading_units: u32, // ArrayPtr + closure_functions: u32, // GrowableObjectArrayPtr + closure_functions_table: u32, // ArrayPtr + pending_classes: u32, // GrowableObjectArrayPtr + record_field_names_map: u32, // ArrayPtr + record_field_names: u32, // ArrayPtr + stack_overflow: u32, // InstancePtr + out_of_memory: u32, // InstancePtr + growable_list_factory: u32, // FunctionPtr + simple_instance_of_function: u32, // FunctionPtr + simple_instance_of_true_function: u32, // FunctionPtr + simple_instance_of_false_function: u32, // FunctionPtr + async_star_stream_controller_add: u32, // FunctionPtr + async_star_stream_controller_add_stream: u32, // FunctionPtr + suspend_state_init_async: u32, // FunctionPtr + suspend_state_await: u32, // FunctionPtr + suspend_state_await_with_type_check: u32, // FunctionPtr + suspend_state_return_async: u32, // FunctionPtr + suspend_state_return_async_not_future: u32, // FunctionPtr + suspend_state_init_async_star: u32, // FunctionPtr + suspend_state_yield_async_star: u32, // FunctionPtr + suspend_state_return_async_star: u32, // FunctionPtr + suspend_state_init_sync_star: u32, // FunctionPtr + suspend_state_suspend_sync_star_at_start: u32, // FunctionPtr + suspend_state_handle_exception: u32, // FunctionPtr + async_star_stream_controller: u32, // ClassPtr + stream_class: u32, // ClassPtr + sync_star_iterator_class: u32, // ClassPtr + async_star_stream_controller_async_star_body: u32, // FieldPtr + sync_star_iterator_current: u32, // FieldPtr + sync_star_iterator_state: u32, // FieldPtr + sync_star_iterator_yield_star_iterable: u32, // FieldPtr + canonicalized_stack_map_entries: u32, // CompressedStackMapsPtr + global_object_pool: u32, // ObjectPoolPtr + unique_dynamic_targets: u32, // ArrayPtr + megamorphic_cache_table: u32, // GrowableObjectArrayPtr + ffi_callback_code: u32, // GrowableObjectArrayPtr + dispatch_table_null_error_stub: u32, // CodePtr + late_initialization_error_stub_with_fpu_regs_stub: u32, // CodePtr + late_initialization_error_stub_without_fpu_regs_stub: u32, // CodePtr + null_error_stub_with_fpu_regs_stub: u32, // CodePtr + null_error_stub_without_fpu_regs_stub: u32, // CodePtr + null_arg_error_stub_with_fpu_regs_stub: u32, // CodePtr + null_arg_error_stub_without_fpu_regs_stub: u32, // CodePtr + null_cast_error_stub_with_fpu_regs_stub: u32, // CodePtr + null_cast_error_stub_without_fpu_regs_stub: u32, // CodePtr + range_error_stub_with_fpu_regs_stub: u32, // CodePtr + range_error_stub_without_fpu_regs_stub: u32, // CodePtr + write_error_stub_with_fpu_regs_stub: u32, // CodePtr + write_error_stub_without_fpu_regs_stub: u32, // CodePtr + field_access_error_stub_with_fpu_regs_stub: u32, // CodePtr + field_access_error_stub_without_fpu_regs_stub: u32, // CodePtr + allocate_mint_with_fpu_regs_stub: u32, // CodePtr + allocate_mint_without_fpu_regs_stub: u32, // CodePtr + stack_overflow_stub_with_fpu_regs_stub: u32, // CodePtr + stack_overflow_stub_without_fpu_regs_stub: u32, // CodePtr + allocate_array_stub: u32, // CodePtr + allocate_mint_stub: u32, // CodePtr + allocate_double_stub: u32, // CodePtr + allocate_float32x4_stub: u32, // CodePtr + allocate_float64x2_stub: u32, // CodePtr + allocate_int32x4_stub: u32, // CodePtr + allocate_int8_array_stub: u32, // CodePtr + allocate_uint8_array_stub: u32, // CodePtr + allocate_uint8_clamped_array_stub: u32, // CodePtr + allocate_int16_array_stub: u32, // CodePtr + allocate_uint16_array_stub: u32, // CodePtr + allocate_int32_array_stub: u32, // CodePtr + allocate_uint32_array_stub: u32, // CodePtr + allocate_int64_array_stub: u32, // CodePtr + allocate_uint64_array_stub: u32, // CodePtr + allocate_float32_array_stub: u32, // CodePtr + allocate_float64_array_stub: u32, // CodePtr + allocate_float32x4_array_stub: u32, // CodePtr + allocate_int32x4_array_stub: u32, // CodePtr + allocate_float64x2_array_stub: u32, // CodePtr + allocate_closure_stub: u32, // CodePtr + allocate_closure_generic_stub: u32, // CodePtr + allocate_closure_ta_stub: u32, // CodePtr + allocate_closure_ta_generic_stub: u32, // CodePtr + allocate_context_stub: u32, // CodePtr + allocate_growable_array_stub: u32, // CodePtr + allocate_object_stub: u32, // CodePtr + allocate_object_parametrized_stub: u32, // CodePtr + allocate_record_stub: u32, // CodePtr + allocate_record2_stub: u32, // CodePtr + allocate_record2_named_stub: u32, // CodePtr + allocate_record3_stub: u32, // CodePtr + allocate_record3_named_stub: u32, // CodePtr + allocate_unhandled_exception_stub: u32, // CodePtr + check_isolate_field_access_stub: u32, // CodePtr + clone_context_stub: u32, // CodePtr + write_barrier_wrappers_stub: u32, // CodePtr + array_write_barrier_stub: u32, // CodePtr + throw_stub: u32, // CodePtr + re_throw_stub: u32, // CodePtr + instance_of_stub: u32, // CodePtr + init_static_field_stub: u32, // CodePtr + init_late_static_field_stub: u32, // CodePtr + init_late_final_static_field_stub: u32, // CodePtr + init_instance_field_stub: u32, // CodePtr + init_late_instance_field_stub: u32, // CodePtr + init_late_final_instance_field_stub: u32, // CodePtr + init_shared_late_static_field_stub: u32, // CodePtr + call_closure_no_such_method_stub: u32, // CodePtr + default_tts_stub: u32, // CodePtr + default_nullable_tts_stub: u32, // CodePtr + top_type_tts_stub: u32, // CodePtr + nullable_type_parameter_tts_stub: u32, // CodePtr + type_parameter_tts_stub: u32, // CodePtr + unreachable_tts_stub: u32, // CodePtr + ffi_callback_functions: u32, // ArrayPtr + resume_stub: u32, // CodePtr + slow_tts_stub: u32, // CodePtr (last field in FullAOT) + } + }; +} + +macro_rules! define_object_store { + ($( $field:ident: u32,)*) => { + #[derive(Default)] + #[repr(C)] + pub(super) struct ObjectStore { + $( $field: u32, )* + } + + impl ObjectStore { + pub const REF_COUNT: usize = [$(stringify!($field)),*].len(); // this is probably better than sizeof(ObjectStore)/sizeof(u32) + + pub fn read(stream: &mut Stream) -> anyhow::Result { + Ok(Self { + $( $field: stream.read_ref_id()?, )* + }) + } + } + }; } -struct IsolateObjectStore -// small one, not too important +object_store_aot_fields!(define_object_store); + +pub(super) struct IsolateObjectStore +// small one, not too important, skip for now { dart_args_1: u32, // ArrayPtr dart_args_2: u32, // ArrayPtr @@ -272,17 +281,44 @@ struct IsolateObjectStore exit_listeners: u32, // GrowableObjectArrayPtr error_listeners: u32, // GrowableObjectArrayPtr } +#[derive(Default)] +pub(super) struct FieldTable { + pub(super) length: usize, + pub(super) field_refs: Vec, +} -struct FieldTable { - // to-do +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(super) enum DispatchTableEntry { + Invalid, + CodeIndex(u64), // this is an index INTO the instruction table } -struct DispatchTable { - // to-do +#[derive(Debug, Default)] +pub(super) struct DispatchTable { + pub(super) first_code_ref: Option, + pub(super) entries: Vec, } +#[derive(Default)] pub struct ProgramRoots { object_store: ObjectStore, field_table: FieldTable, + shared_field_table: FieldTable, dispatch_table: DispatchTable, } + +impl ProgramRoots { + pub(crate) fn new( + object_store: ObjectStore, + field_table: FieldTable, + shared_field_table: FieldTable, + dispatch_table: DispatchTable, + ) -> Self { + Self { + object_store, + field_table, + shared_field_table, + dispatch_table, + } + } +} diff --git a/crates/flutterdec-serwalker/src/snapshot.rs b/crates/flutterdec-serwalker/src/snapshot.rs index 497753c..4ea8897 100644 --- a/crates/flutterdec-serwalker/src/snapshot.rs +++ b/crates/flutterdec-serwalker/src/snapshot.rs @@ -3,6 +3,7 @@ use std::collections::HashMap; use crate::cluster::{decide_cluster, Cluster}; use crate::constants::{self, ClassId, MAGIC_BYTES, UNSIGNED_M}; use crate::program_roots::structs::ProgramRoots; +use crate::program_roots::{parse_dispatch_table, parse_field_table, parse_object_store}; use crate::stream::Stream; use crate::utils::{decode_tags, DecodedTags}; @@ -137,7 +138,17 @@ impl DataSnapshot { } fn parse_roots(&mut self, stream: &mut Stream) -> anyhow::Result<()> { - // to-do (parse object store and field tables) + let object_store = parse_object_store(stream)?; + let field_table = parse_field_table(stream)?; + let shared_field_table = parse_field_table(stream)?; + let dispatch_table = parse_dispatch_table(stream)?; + + self.roots = ProgramRoots::new( + object_store, + field_table, + shared_field_table, + dispatch_table, + ); Ok(()) } } diff --git a/crates/flutterdec-serwalker/src/stream.rs b/crates/flutterdec-serwalker/src/stream.rs index 8e8a8ff..e7015dd 100644 --- a/crates/flutterdec-serwalker/src/stream.rs +++ b/crates/flutterdec-serwalker/src/stream.rs @@ -26,7 +26,7 @@ impl Stream { Ok(()) } - fn take(&mut self, n: usize) -> Result<&'a [u8]> { + fn take(&mut self, n: usize) -> Result<&[u8]> { let end = self .curr_stream_offset .checked_add(n) @@ -140,7 +140,7 @@ impl Stream { } /// Reads a block of bytes and returns a reference to the slice (Zero-copy, highly recommended for large payloads) - pub fn read_bytes_zero_copy(&mut self, len: usize) -> Result<&'a [u8]> { + pub fn read_bytes_zero_copy(&mut self, len: usize) -> Result<&[u8]> { self.take(len) } } From e5e142f00235c5607baa5a4a540d3e32550be9c7 Mon Sep 17 00:00:00 2001 From: kalixtez Date: Sun, 2 Aug 2026 18:27:59 -0500 Subject: [PATCH 49/50] feat(serwalker): added instruction table parsing, finished the implementation for the rest of variable length clusters, modified some raw object layouts to match their C++ counterparts, added an instructiontable field to the DataSnapshot struct and added the is_immutable and is_canonical flags as field members of the Cluster struct definitions --- .../flutterdec-serwalker/src/cluster/mod.rs | 701 +++++++++++++++--- crates/flutterdec-serwalker/src/constants.rs | 2 + .../src/instruction_table.rs | 51 ++ crates/flutterdec-serwalker/src/lib.rs | 2 + .../src/program_roots/mod.rs | 19 +- .../src/program_roots/structs.rs | 2 +- .../src/raw_object/mod.rs | 78 +- crates/flutterdec-serwalker/src/snapshot.rs | 25 +- crates/flutterdec-serwalker/src/stream.rs | 20 +- crates/flutterdec-serwalker/src/utils.rs | 19 +- 10 files changed, 780 insertions(+), 139 deletions(-) create mode 100644 crates/flutterdec-serwalker/src/instruction_table.rs diff --git a/crates/flutterdec-serwalker/src/cluster/mod.rs b/crates/flutterdec-serwalker/src/cluster/mod.rs index 4b16fde..51aedcf 100644 --- a/crates/flutterdec-serwalker/src/cluster/mod.rs +++ b/crates/flutterdec-serwalker/src/cluster/mod.rs @@ -1,7 +1,4 @@ -use std::env::var; -use std::usize; - -use crate::constants::{ClassId, ClassId::*, SIGNED_M, UNSIGNED_M}; +use crate::constants::{ClassId, ClassId::*}; use crate::raw_object::*; use crate::stream::Stream; use crate::DECLARE_FIXED_LENGTH_CLUSTER; @@ -9,6 +6,13 @@ use crate::DECLARE_VARIABLE_LENGTH_CLUSTER; use crate::FFI_TYPES_LIST; pub trait Cluster { + fn set_metadata( + &mut self, + tags: u32, + cid: ClassId, + is_deeply_immutable: bool, + is_canonical: bool, + ); fn is_fixed_len(&self) -> bool; fn read_alloc(&mut self, last_ref_id: &mut u64, stream: &mut Stream) -> anyhow::Result; fn read_fill(&mut self, stream: &mut Stream) -> anyhow::Result; @@ -379,153 +383,637 @@ DECLARE_FIXED_LENGTH_CLUSTER!(WeakProperty, WeakPropertyCluster, |_self, stream| } }); -DECLARE_VARIABLE_LENGTH_CLUSTER!(Map, MapCluster); -DECLARE_VARIABLE_LENGTH_CLUSTER!(Set, SetCluster); -DECLARE_VARIABLE_LENGTH_CLUSTER!(Instance, InstanceCluster); -DECLARE_VARIABLE_LENGTH_CLUSTER!(TypedData, TypedDataCluster); -DECLARE_VARIABLE_LENGTH_CLUSTER!(Class, ClassCluster); -DECLARE_VARIABLE_LENGTH_CLUSTER!(TypeArguments, TypeArgumentsCluster); DECLARE_VARIABLE_LENGTH_CLUSTER!(Code, CodeCluster); DECLARE_VARIABLE_LENGTH_CLUSTER!(ObjectPool, ObjectPoolCluster); -DECLARE_VARIABLE_LENGTH_CLUSTER!(ExceptionHandlers, ExceptionHandlersCluster); -DECLARE_VARIABLE_LENGTH_CLUSTER!(Context, ContextCluster); -DECLARE_VARIABLE_LENGTH_CLUSTER!(ContextScope, ContextScopeCluster); -DECLARE_VARIABLE_LENGTH_CLUSTER!(Mint, MintCluster); -DECLARE_VARIABLE_LENGTH_CLUSTER!(Float32x4, Float32x4Cluster); -DECLARE_VARIABLE_LENGTH_CLUSTER!(Float64x2, Float64x2Cluster); -DECLARE_VARIABLE_LENGTH_CLUSTER!(Record, RecordCluster); -DECLARE_VARIABLE_LENGTH_CLUSTER!(Array, ArrayCluster); -DECLARE_VARIABLE_LENGTH_CLUSTER!(WeakArray, WeakArrayCluster); -DECLARE_VARIABLE_LENGTH_CLUSTER!(ImmutableArray, ImmutableArrayCluster); -DECLARE_VARIABLE_LENGTH_CLUSTER!(ConstMap, ConstMapCluster); -DECLARE_VARIABLE_LENGTH_CLUSTER!(ConstSet, ConstSetCluster); -DECLARE_VARIABLE_LENGTH_CLUSTER!(CodeSourceMap, CodeSourceMapCluster); -DECLARE_VARIABLE_LENGTH_CLUSTER!(CompressedStackMaps, CompressedStackMapsCluster); -impl Cluster for CompressedStackMapsCluster { - fn is_fixed_len(&self) -> bool { - false - } +macro_rules! IMPLEMENT_VARIABLE_LENGTH_CLUSTER { + ( + $cluster_name:ident, + |$alloc_self:ident, $alloc_stream:ident| $alloc_impl:block, + |$fill_self:ident, $fill_stream:ident| $fill_impl:block + ) => { + impl Cluster for $cluster_name { + fn set_metadata( + &mut self, + tags: u32, + cid: ClassId, + is_deeply_immutable: bool, + is_canonical: bool, + ) { + self.tags = tags; + self.cid = cid; + self.is_deeply_immutable = is_deeply_immutable; + self.is_canonical = is_canonical; + } - fn read_alloc(&mut self, last_ref_id: &mut u64, stream: &mut Stream) -> anyhow::Result { - self.start_of_alloc = stream.get_current_pos(); - self.first_ref_id = *last_ref_id as u32; + fn is_fixed_len(&self) -> bool { + false + } - self.obj_count = stream.read_unsigned()?; - for _obj_idx in 0..self.obj_count { - let mut obj = Box::::default(); - obj.length = stream.read_unsigned()? as u32; + fn read_alloc( + &mut self, + last_ref_id: &mut u64, + stream: &mut Stream, + ) -> anyhow::Result { + self.start_of_alloc = stream.get_current_pos(); + self.first_ref_id = *last_ref_id as u32; - self.objs.push(obj); + let $alloc_self = &mut *self; + let $alloc_stream = &mut *stream; + $alloc_impl + + *last_ref_id += $alloc_self.obj_count; + $alloc_self.end_of_alloc = $alloc_stream.get_current_pos(); + Ok($alloc_self.end_of_alloc - $alloc_self.start_of_alloc) + } + + fn read_fill(&mut self, stream: &mut Stream) -> anyhow::Result { + self.start_of_fill = stream.get_current_pos(); + + let $fill_self = &mut *self; + let $fill_stream = &mut *stream; + $fill_impl + + $fill_self.end_of_fill = $fill_stream.get_current_pos(); + Ok($fill_self.end_of_fill - $fill_self.start_of_fill) + } } + }; +} - *last_ref_id += self.obj_count; - self.end_of_alloc = stream.get_current_pos(); +fn typed_data_element_size(cid: ClassId) -> anyhow::Result { + let size = match cid { + TypedDataInt8ArrayCid | TypedDataUint8ArrayCid | TypedDataUint8ClampedArrayCid => 1, + TypedDataInt16ArrayCid | TypedDataUint16ArrayCid => 2, + TypedDataInt32ArrayCid | TypedDataUint32ArrayCid | TypedDataFloat32ArrayCid => 4, + TypedDataInt64ArrayCid | TypedDataUint64ArrayCid | TypedDataFloat64ArrayCid => 8, + TypedDataFloat32x4ArrayCid | TypedDataInt32x4ArrayCid | TypedDataFloat64x2ArrayCid => 16, + _ => anyhow::bail!("class {:?} is not an internal TypedData class", cid), + }; + Ok(size) +} - Ok(self.end_of_alloc - self.start_of_alloc) +DECLARE_VARIABLE_LENGTH_CLUSTER!(Map, MapCluster); +IMPLEMENT_VARIABLE_LENGTH_CLUSTER!( + MapCluster, + |cluster, stream| { + cluster.obj_count = stream.read_unsigned()?; + for _ in 0..cluster.obj_count { + cluster.objs.push(Box::::default()); + } + }, + |cluster, stream| { + for obj in &mut cluster.objs { + obj.type_arguments = stream.read_ref_id()?; + obj.hash_mask = stream.read_ref_id()?; + obj.data = stream.read_ref_id()?; + obj.used_data = stream.read_ref_id()?; + obj.deleted_keys = stream.read_ref_id()?; + // UntaggedLinkedHashBase::to_snapshot excludes the rebuilt index_. + } } +); - fn read_fill(&mut self, stream: &mut Stream) -> anyhow::Result { - self.start_of_fill = stream.get_current_pos(); - for obj_idx in 0..self.obj_count { - let obj = &mut self.objs[obj_idx as usize]; - obj.flags_and_size = stream.read_unsigned()? as u32; +DECLARE_VARIABLE_LENGTH_CLUSTER!(Set, SetCluster); +IMPLEMENT_VARIABLE_LENGTH_CLUSTER!( + SetCluster, + |cluster, stream| { + cluster.obj_count = stream.read_unsigned()?; + for _ in 0..cluster.obj_count { + cluster.objs.push(Box::::default()); + } + }, + |cluster, stream| { + for obj in &mut cluster.objs { + obj.type_arguments = stream.read_ref_id()?; + obj.hash_mask = stream.read_ref_id()?; + obj.data = stream.read_ref_id()?; + obj.used_data = stream.read_ref_id()?; + obj.deleted_keys = stream.read_ref_id()?; + } + } +); - obj.data = stream.read_bytes(obj.length as usize)?; +DECLARE_VARIABLE_LENGTH_CLUSTER!(Instance, InstanceCluster); +IMPLEMENT_VARIABLE_LENGTH_CLUSTER!( + InstanceCluster, + |cluster, stream| { + cluster.obj_count = stream.read_unsigned()?; + let next_field_offset_in_words = stream.read()? as i32; + let instance_size_in_words = stream.read()? as i32; + for _ in 0..cluster.obj_count { + cluster.objs.push(Box::new(Instance { + next_field_offset_in_words, + instance_size_in_words, + ..Instance::default() + })); } + }, + |cluster, stream| { + const FIRST_INSTANCE_FIELD_WORD: i32 = 2; - self.end_of_fill = stream.get_current_pos(); + let bitmap = stream.read_unsigned()?; + for obj in &mut cluster.objs { + obj.unboxed_fields_bitmap = bitmap; + if obj.next_field_offset_in_words < FIRST_INSTANCE_FIELD_WORD { + anyhow::bail!( + "invalid instance next-field offset {}", + obj.next_field_offset_in_words + ); + } - Ok(self.end_of_fill - self.start_of_fill) + for word_index in FIRST_INSTANCE_FIELD_WORD..obj.next_field_offset_in_words { + let mask = 1_u64.checked_shl(word_index as u32).unwrap_or(0); + if bitmap & mask != 0 { + // ReadWordWith32BitReads reads two encoded 32-bit chunks + // for a 64-bit target word. + let low = stream.read()? as u32 as u64; + let high = stream.read()? as u32 as u64; + obj.fields.push(InstanceField::Unboxed(low | (high << 32))); + } else { + obj.fields + .push(InstanceField::Reference(stream.read_ref_id()?)); + } + } + } } -} - -DECLARE_VARIABLE_LENGTH_CLUSTER!(PcDescriptors, PcDescriptorsCluster); +); -impl Cluster for PcDescriptorsCluster { - fn is_fixed_len(&self) -> bool { - false +DECLARE_VARIABLE_LENGTH_CLUSTER!(TypedData, TypedDataCluster); +IMPLEMENT_VARIABLE_LENGTH_CLUSTER!( + TypedDataCluster, + |cluster, stream| { + cluster.obj_count = stream.read_unsigned()?; + for _ in 0..cluster.obj_count { + let length = stream.read_unsigned()? as usize; + cluster.objs.push(Box::new(TypedData { + length, + ..TypedData::default() + })); + } + }, + |cluster, stream| { + let element_size = typed_data_element_size(cluster.cid)?; + for obj in &mut cluster.objs { + let length = stream.read_unsigned()? as usize; + let byte_length = length + .checked_mul(element_size) + .ok_or_else(|| anyhow::anyhow!("TypedData byte length overflow"))?; + obj.length = length; + obj.data = stream.read_bytes(byte_length)?; + } } +); - fn read_alloc(&mut self, last_ref_id: &mut u64, stream: &mut Stream) -> anyhow::Result { - self.start_of_alloc = stream.get_current_pos(); - self.first_ref_id = *last_ref_id as u32; +DECLARE_VARIABLE_LENGTH_CLUSTER!(Class, ClassCluster); +IMPLEMENT_VARIABLE_LENGTH_CLUSTER!( + ClassCluster, + |cluster, stream| { + let predefined_count = stream.read_unsigned()?; + for _ in 0..predefined_count { + cluster.objs.push(Box::new(Class { + id: stream.read()? as i32, + is_predefined: true, + ..Class::default() + })); + } - self.obj_count = stream.read_unsigned()?; + let regular_count = stream.read_unsigned()?; + for _ in 0..regular_count { + cluster.objs.push(Box::::default()); + } + cluster.obj_count = predefined_count + regular_count; + }, + |cluster, stream| { + const TOP_LEVEL_CID_OFFSET: i32 = 1 << 20; - for _obj_idx in 0..self.obj_count { - let mut obj = Box::::default(); - let length = stream.read_unsigned()?; + for obj in &mut cluster.objs { + obj.name = stream.read_ref_id()?; + obj.functions = stream.read_ref_id()?; + obj.functions_hash_table = stream.read_ref_id()?; + obj.fields = stream.read_ref_id()?; + obj.offset_in_words_to_field = stream.read_ref_id()?; + obj.interfaces = stream.read_ref_id()?; + obj.script = stream.read_ref_id()?; + obj.library = stream.read_ref_id()?; + obj.type_parameters = stream.read_ref_id()?; + obj.super_type = stream.read_ref_id()?; + obj.constants = stream.read_ref_id()?; + obj.declaration_type = stream.read_ref_id()?; + obj.invocation_dispatcher_cache = stream.read_ref_id()?; - obj.length = length as u32; - self.objs.push(obj); + obj.id = stream.read()? as i32; + obj.target_instance_size_in_words = stream.read()? as i32; + obj.target_next_field_offset_in_words = stream.read()? as i32; + obj.target_type_arguments_field_offset_in_words = stream.read()? as i32; + obj.num_type_arguments = stream.read()? as i16; + obj.num_native_fields = stream.read()? as u16; + obj.state_bits = stream.read()? as u32; + if obj.id < TOP_LEVEL_CID_OFFSET { + obj.unboxed_fields_bitmap = Some(stream.read_unsigned()?); + } } + } +); - *last_ref_id += self.obj_count; +DECLARE_VARIABLE_LENGTH_CLUSTER!(TypeArguments, TypeArgumentsCluster); +IMPLEMENT_VARIABLE_LENGTH_CLUSTER!( + TypeArgumentsCluster, + |cluster, stream| { + cluster.obj_count = stream.read_unsigned()?; + for _ in 0..cluster.obj_count { + let length = stream.read_unsigned()? as i32; + cluster.objs.push(Box::new(TypeArguments { + length, + ..TypeArguments::default() + })); + } - self.end_of_alloc = stream.get_current_pos(); - Ok(self.end_of_alloc - self.start_of_alloc) + // a canonical cluster in the root loading unit carries the canonical + // hash-set layout after its ordinary allocation records + + // so this is just for the canonical TypeArguments cluster (if any) + if cluster.is_canonical { + let _table_length = stream.read_unsigned()?; + let first_element = stream.read_unsigned()?; + if first_element > cluster.obj_count { + anyhow::bail!( + "canonical TypeArguments first element {first_element} exceeds count {}", + cluster.obj_count + ); + } + for _ in first_element..cluster.obj_count { + let _gap = stream.read_unsigned()?; + } + } + }, + |cluster, stream| { + for obj in &mut cluster.objs { + let length = stream.read_unsigned()? as i32; + obj.length = length; + obj.hash = stream.read()? as i32; + obj.nullability = stream.read_unsigned()? as i32; + obj.instantiations = stream.read_ref_id()?; + for _ in 0..length { + obj.types.push(stream.read_ref_id()?); + } + } } +); - fn read_fill(&mut self, stream: &mut Stream) -> anyhow::Result { - self.start_of_fill = stream.get_current_pos(); +DECLARE_VARIABLE_LENGTH_CLUSTER!(ExceptionHandlers, ExceptionHandlersCluster); +IMPLEMENT_VARIABLE_LENGTH_CLUSTER!( + ExceptionHandlersCluster, + |cluster, stream| { + cluster.obj_count = stream.read_unsigned()?; + for _ in 0..cluster.obj_count { + let num_entries = stream.read_unsigned()? as usize; + cluster.objs.push(Box::new(ExceptionHandlers { + num_entries, + ..ExceptionHandlers::default() + })); + } + }, + |cluster, stream| { + for obj in &mut cluster.objs { + obj.packed_fields = stream.read_unsigned()? as u32; + obj.handled_types_data = stream.read_ref_id()?; + for _ in 0..obj.num_entries { + obj.entries.push(ExceptionHandlerInfo { + handler_pc_offset: stream.read()? as u32, + outer_try_index: stream.read()? as i16, + needs_stacktrace: stream.read_byte()? as i8, + has_catch_all: stream.read_byte()? as i8, + is_generated: stream.read_byte()? as i8, + }); + } + } + } +); - for obj_idx in 0..self.obj_count { - let length = stream.read_unsigned()?; // its saved twice - let variable_size_data = stream.read_bytes(length as usize)?; +DECLARE_VARIABLE_LENGTH_CLUSTER!(Context, ContextCluster); +IMPLEMENT_VARIABLE_LENGTH_CLUSTER!( + ContextCluster, + |cluster, stream| { + cluster.obj_count = stream.read_unsigned()?; + for _ in 0..cluster.obj_count { + let num_variables = stream.read_unsigned()? as i32; + cluster.objs.push(Box::new(Context { + num_variables, + ..Context::default() + })); + } + }, + |cluster, stream| { + for obj in &mut cluster.objs { + let num_variables = stream.read_unsigned()? as i32; + obj.num_variables = num_variables; + obj.parent = stream.read_ref_id()?; + for _ in 0..num_variables { + obj.variables.push(stream.read_ref_id()?); + } + } + } +); - let obj = &mut self.objs[obj_idx as usize]; - obj.data = variable_size_data; +DECLARE_VARIABLE_LENGTH_CLUSTER!(ContextScope, ContextScopeCluster); +IMPLEMENT_VARIABLE_LENGTH_CLUSTER!( + ContextScopeCluster, + |cluster, stream| { + cluster.obj_count = stream.read_unsigned()?; + for _ in 0..cluster.obj_count { + let num_variables = stream.read_unsigned()? as i32; + cluster.objs.push(Box::new(ContextScope { + num_variables, + ..ContextScope::default() + })); } + }, + |cluster, stream| { + const VARIABLE_DESC_REF_COUNT: i32 = 10; - Ok(self.end_of_fill - self.start_of_fill) + for obj in &mut cluster.objs { + let num_variables = stream.read_unsigned()? as i32; + obj.num_variables = num_variables; + obj.is_implicit = stream.read_byte()? != 0; + for _ in 0..num_variables * VARIABLE_DESC_REF_COUNT { + obj.variables.push(stream.read_ref_id()?); + } + } } -} +); -//DECLARE_VARIABLE_LENGTH_CLUSTER!(OneByteString, OneByteStringCluster); These only exist when NO COMPRESSED_POINTERS -//DECLARE_VARIABLE_LENGTH_CLUSTER!(TwoByteString, TwoByteStringCluster); -DECLARE_VARIABLE_LENGTH_CLUSTER!(_String, _StringCluster); +DECLARE_VARIABLE_LENGTH_CLUSTER!(Mint, MintCluster); +IMPLEMENT_VARIABLE_LENGTH_CLUSTER!( + MintCluster, + |cluster, stream| { + cluster.obj_count = stream.read_unsigned()?; + for _ in 0..cluster.obj_count { + cluster.objs.push(Box::new(Mint { + value: stream.read()? as i64, + })); + } + }, + |_cluster, _stream| {} +); -impl Cluster for _StringCluster { - fn is_fixed_len(&self) -> bool { - false +DECLARE_VARIABLE_LENGTH_CLUSTER!(Float32x4, Float32x4Cluster); +IMPLEMENT_VARIABLE_LENGTH_CLUSTER!( + Float32x4Cluster, + |cluster, stream| { + cluster.obj_count = stream.read_unsigned()?; + for _ in 0..cluster.obj_count { + cluster.objs.push(Box::::default()); + } + }, + |cluster, stream| { + for obj in &mut cluster.objs { + obj.value = stream.read_bytes(16)?; + } } +); - fn read_alloc(&mut self, last_ref_id: &mut u64, stream: &mut Stream) -> anyhow::Result { - self.start_of_alloc = stream.get_current_pos(); - self.first_ref_id = *last_ref_id as u32; +DECLARE_VARIABLE_LENGTH_CLUSTER!(Float64x2, Float64x2Cluster); +IMPLEMENT_VARIABLE_LENGTH_CLUSTER!( + Float64x2Cluster, + |cluster, stream| { + cluster.obj_count = stream.read_unsigned()?; + for _ in 0..cluster.obj_count { + cluster.objs.push(Box::::default()); + } + }, + |cluster, stream| { + for obj in &mut cluster.objs { + obj.value = stream.read_bytes(16)?; + } + } +); - self.obj_count = stream.read_unsigned()?; +DECLARE_VARIABLE_LENGTH_CLUSTER!(Record, RecordCluster); +IMPLEMENT_VARIABLE_LENGTH_CLUSTER!( + RecordCluster, + |cluster, stream| { + cluster.obj_count = stream.read_unsigned()?; + for _ in 0..cluster.obj_count { + let num_fields = stream.read_unsigned()? as usize; + cluster.objs.push(Box::new(Record { + num_fields, + ..Record::default() + })); + } + }, + |cluster, stream| { + for obj in &mut cluster.objs { + obj.shape = stream.read_unsigned()? as i32; + for _ in 0..obj.num_fields { + obj.fields.push(stream.read_ref_id()?); + } + } + } +); - for _obj_idx in 0..self.obj_count { - let mut obj = Box::<_String>::default(); - let encoded = stream.read_unsigned()?; - obj.string_type = if encoded & 1 == 1 { - StrType::TwoByte - } else { - StrType::OneByte - }; +DECLARE_VARIABLE_LENGTH_CLUSTER!(Array, ArrayCluster); +IMPLEMENT_VARIABLE_LENGTH_CLUSTER!( + ArrayCluster, + |cluster, stream| { + cluster.obj_count = stream.read_unsigned()?; + for _ in 0..cluster.obj_count { + let length = stream.read_unsigned()? as i32; + cluster.objs.push(Box::new(Array { + length, + ..Array::default() + })); + } + }, + |cluster, stream| { + for obj in &mut cluster.objs { + let length = stream.read_unsigned()? as i32; + obj.length = length; + obj.type_arguments = stream.read_ref_id()?; + for _ in 0..length { + obj.elements.push(stream.read_ref_id()?); + } + } + } +); - obj.length = (encoded >> 1) as i32; - self.objs.push(obj); +DECLARE_VARIABLE_LENGTH_CLUSTER!(WeakArray, WeakArrayCluster); +IMPLEMENT_VARIABLE_LENGTH_CLUSTER!( + WeakArrayCluster, + |cluster, stream| { + cluster.obj_count = stream.read_unsigned()?; + for _ in 0..cluster.obj_count { + let length = stream.read_unsigned()? as i32; + cluster.objs.push(Box::new(WeakArray { + length, + ..WeakArray::default() + })); } + }, + |cluster, stream| { + for obj in &mut cluster.objs { + let length = stream.read_unsigned()? as i32; + obj.length = length; + for _ in 0..length { + obj.elements.push(stream.read_ref_id()?); + } + } + } +); - *last_ref_id += self.obj_count; - self.end_of_alloc = stream.get_current_pos(); +DECLARE_VARIABLE_LENGTH_CLUSTER!(ImmutableArray, ImmutableArrayCluster); +IMPLEMENT_VARIABLE_LENGTH_CLUSTER!( + ImmutableArrayCluster, + |cluster, stream| { + cluster.obj_count = stream.read_unsigned()?; + for _ in 0..cluster.obj_count { + let length = stream.read_unsigned()? as i32; + cluster.objs.push(Box::new(ImmutableArray { + length, + ..ImmutableArray::default() + })); + } + }, + |cluster, stream| { + for obj in &mut cluster.objs { + let length = stream.read_unsigned()? as i32; + obj.length = length; + obj.type_arguments = stream.read_ref_id()?; + for _ in 0..length { + obj.elements.push(stream.read_ref_id()?); + } + } + } +); - Ok(self.end_of_alloc - self.start_of_alloc) +DECLARE_VARIABLE_LENGTH_CLUSTER!(ConstMap, ConstMapCluster); +IMPLEMENT_VARIABLE_LENGTH_CLUSTER!( + ConstMapCluster, + |cluster, stream| { + cluster.obj_count = stream.read_unsigned()?; + for _ in 0..cluster.obj_count { + cluster.objs.push(Box::::default()); + } + }, + |cluster, stream| { + for obj in &mut cluster.objs { + obj.type_arguments = stream.read_ref_id()?; + obj.hash_mask = stream.read_ref_id()?; + obj.data = stream.read_ref_id()?; + obj.used_data = stream.read_ref_id()?; + obj.deleted_keys = stream.read_ref_id()?; + } } +); - fn read_fill(&mut self, stream: &mut Stream) -> anyhow::Result { - self.start_of_fill = stream.get_current_pos(); +DECLARE_VARIABLE_LENGTH_CLUSTER!(ConstSet, ConstSetCluster); +IMPLEMENT_VARIABLE_LENGTH_CLUSTER!( + ConstSetCluster, + |cluster, stream| { + cluster.obj_count = stream.read_unsigned()?; + for _ in 0..cluster.obj_count { + cluster.objs.push(Box::::default()); + } + }, + |cluster, stream| { + for obj in &mut cluster.objs { + obj.type_arguments = stream.read_ref_id()?; + obj.hash_mask = stream.read_ref_id()?; + obj.data = stream.read_ref_id()?; + obj.used_data = stream.read_ref_id()?; + obj.deleted_keys = stream.read_ref_id()?; + } + } +); - for obj_idx in 0..self.obj_count { - let _encoded = stream.read_unsigned()?; // why is this here twice? Huh? - let obj = self.objs.get_mut(obj_idx as usize); +DECLARE_VARIABLE_LENGTH_CLUSTER!(CodeSourceMap, CodeSourceMapCluster); +IMPLEMENT_VARIABLE_LENGTH_CLUSTER!( + CodeSourceMapCluster, + |cluster, stream| { + cluster.obj_count = stream.read_unsigned()?; + for _ in 0..cluster.obj_count { + let length = stream.read_unsigned()? as u32; + cluster.objs.push(Box::new(CodeSourceMap { + length, + ..CodeSourceMap::default() + })); + } + }, + |cluster, stream| { + for obj in &mut cluster.objs { + let length = stream.read_unsigned()? as u32; + obj.length = length; + obj.data = stream.read_bytes(length as usize)?; + } + } +); + +DECLARE_VARIABLE_LENGTH_CLUSTER!(CompressedStackMaps, CompressedStackMapsCluster); +IMPLEMENT_VARIABLE_LENGTH_CLUSTER!( + CompressedStackMapsCluster, + |cluster, stream| { + cluster.obj_count = stream.read_unsigned()?; + for _ in 0..cluster.obj_count { + let length = stream.read_unsigned()? as u32; + cluster.objs.push(Box::new(CompressedStackMaps { + length, + ..CompressedStackMaps::default() + })); + } + }, + |cluster, stream| { + for obj in &mut cluster.objs { + obj.flags_and_size = stream.read_unsigned()? as u32; + obj.data = stream.read_bytes(obj.length as usize)?; + } + } +); - let obj = - obj.unwrap_or_else(|| panic!("Couldn't unwrap... No string at index {obj_idx}")); +DECLARE_VARIABLE_LENGTH_CLUSTER!(PcDescriptors, PcDescriptorsCluster); +IMPLEMENT_VARIABLE_LENGTH_CLUSTER!( + PcDescriptorsCluster, + |cluster, stream| { + cluster.obj_count = stream.read_unsigned()?; + for _ in 0..cluster.obj_count { + let length = stream.read_unsigned()? as u32; + cluster.objs.push(Box::new(PcDescriptors { + length, + ..PcDescriptors::default() + })); + } + }, + |cluster, stream| { + for obj in &mut cluster.objs { + let length = stream.read_unsigned()?; // its saved twice + obj.data = stream.read_bytes(length as usize)?; + } + } +); +//DECLARE_VARIABLE_LENGTH_CLUSTER!(OneByteString, OneByteStringCluster); These only exist when NO COMPRESSED_POINTERS +//DECLARE_VARIABLE_LENGTH_CLUSTER!(TwoByteString, TwoByteStringCluster); +DECLARE_VARIABLE_LENGTH_CLUSTER!(_String, _StringCluster); +IMPLEMENT_VARIABLE_LENGTH_CLUSTER!( + _StringCluster, + |cluster, stream| { + cluster.obj_count = stream.read_unsigned()?; + for _ in 0..cluster.obj_count { + let encoded = stream.read_unsigned()?; + let string_type = if encoded & 1 == 1 { + StrType::TwoByte + } else { + StrType::OneByte + }; + cluster.objs.push(Box::new(_String { + string_type, + length: (encoded >> 1) as i32, + .._String::default() + })); + } + }, + |cluster, stream| { + for obj in &mut cluster.objs { + let _encoded = stream.read_unsigned()?; // why is this here twice? Huh? match obj.string_type { StrType::OneByte => { // OneByteString payload is Latin-1, not UTF-8. Bytes 0x80..=0xFF @@ -554,8 +1042,5 @@ impl Cluster for _StringCluster { } } } - - self.end_of_fill = stream.get_current_pos(); - Ok(self.end_of_fill - self.start_of_fill) } -} +); diff --git a/crates/flutterdec-serwalker/src/constants.rs b/crates/flutterdec-serwalker/src/constants.rs index 3cb5430..76f9104 100644 --- a/crates/flutterdec-serwalker/src/constants.rs +++ b/crates/flutterdec-serwalker/src/constants.rs @@ -26,6 +26,8 @@ pub const SMI_SHIFT: usize = 1usize; pub const VERSION_HASH_LENGTH: usize = 32usize; +pub const HEADER_SIZE: usize = 64; + macro_rules! DEFINE_CLASS_ID { ( $( $name:ident = $val:expr ),* ) => { #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] diff --git a/crates/flutterdec-serwalker/src/instruction_table.rs b/crates/flutterdec-serwalker/src/instruction_table.rs new file mode 100644 index 0000000..8503dd8 --- /dev/null +++ b/crates/flutterdec-serwalker/src/instruction_table.rs @@ -0,0 +1,51 @@ +use crate::{instruction_table, stream::Stream}; + +#[derive(Default)] +pub struct InstructionTable +// in reality this is the representation of InstructionTable::Data of the C++ code +{ + canonical_stack_map_entries_offset: usize, + length: usize, + first_entry_with_code: usize, + padding: usize, + data: Vec, +} + +// in AOT mode, the instruction table is used to resolve the entry points +// for Function, Code, Closure, etc... Objects +struct DataEntry { + pc_offset: usize, + stack_map_offset: usize, +} + +pub fn parse_instr_table_from_rodata(stream: &mut Stream) -> anyhow::Result { + // ROData objects are wrapped inside OneByteString objects, so we need to read the syntetic fields first. + let mut tags = stream.read_raw_u64()?; + let mut data_byte_size = stream.read_raw_u64()?; + // i wont do anything with them for now, for a normal snapshot, the tags should be a OneByteString cid + // and data_byte_size should be the size of the contained InstructionsTable::Data + // i.e 4 * sizeof(u32) + (2*sizeof(u32)) * length + // corresponding to the four fields below + the size of each entry + + let mut instruction_table = InstructionTable { + canonical_stack_map_entries_offset: stream.read_raw_u32()? as usize, + length: stream.read_raw_u32()? as usize, + first_entry_with_code: stream.read_raw_u32()? as usize, + padding: stream.read_raw_u32()? as usize, + data: Vec::default(), + }; + + // knowing what i explained in the comment above, we have that necessarily + // 4 * sizeof(u32) + (2*sizeof(u32)) * length <= data_byte_size + + for _idx in 0..instruction_table.length { + let entry = DataEntry { + pc_offset: stream.read_raw_u32()? as usize, + stack_map_offset: stream.read_raw_u32()? as usize, + }; + + instruction_table.data.push(entry); + } + + Ok(instruction_table) +} diff --git a/crates/flutterdec-serwalker/src/lib.rs b/crates/flutterdec-serwalker/src/lib.rs index e4edc42..4bbf70a 100644 --- a/crates/flutterdec-serwalker/src/lib.rs +++ b/crates/flutterdec-serwalker/src/lib.rs @@ -2,7 +2,9 @@ mod constants; mod utils; mod cluster; +mod instruction_table; mod program_roots; + mod raw_object; mod snapshot; diff --git a/crates/flutterdec-serwalker/src/program_roots/mod.rs b/crates/flutterdec-serwalker/src/program_roots/mod.rs index de44c81..49261ad 100644 --- a/crates/flutterdec-serwalker/src/program_roots/mod.rs +++ b/crates/flutterdec-serwalker/src/program_roots/mod.rs @@ -12,7 +12,7 @@ pub fn parse_field_table(stream: &mut Stream) -> anyhow::Result { let table_length = stream.read_unsigned()?; // extremely unlikely but one never knows - field_table.length = table_length.try_into().map_err(|table_length: u64| { + field_table.length = table_length.try_into().map_err(|_| { anyhow::anyhow!( "field table of length {} does not fit in usize", table_length @@ -20,7 +20,7 @@ pub fn parse_field_table(stream: &mut Stream) -> anyhow::Result { })?; // field_table.field_refs = Vec::with_capacity(table_length as usize); // passed the try_into above, safe to raw cast - + for _ in 0..table_length { let refid = stream.read_ref_id()?; field_table.field_refs.push(refid); @@ -36,8 +36,9 @@ pub fn parse_dispatch_table(stream: &mut Stream) -> anyhow::Result anyhow::Result { $emit! { - // Layout for the 3.12 Dart version, and as early as 3.11 + // Layout for the 3.12 Dart version, and as early as 3.11 list_class: u32, // ClassPtr map_class: u32, // ClassPtr set_class: u32, // ClassPtr diff --git a/crates/flutterdec-serwalker/src/raw_object/mod.rs b/crates/flutterdec-serwalker/src/raw_object/mod.rs index 3fd3d5e..5939efa 100644 --- a/crates/flutterdec-serwalker/src/raw_object/mod.rs +++ b/crates/flutterdec-serwalker/src/raw_object/mod.rs @@ -1,5 +1,3 @@ -use std::default; - pub type Smi = i32; #[derive(Default)] @@ -9,6 +7,8 @@ pub struct Object { #[derive(Default)] pub struct Class { + pub id: i32, + pub is_predefined: bool, pub name: u32, // StringPtr pub user_name: u32, // StringPtr pub functions: u32, // ArrayPtr @@ -38,6 +38,7 @@ pub struct Class { pub target_instance_size_in_words: i32, pub target_type_arguments_field_offset_in_words: i32, pub target_next_field_offset_in_words: i32, + pub unboxed_fields_bitmap: Option, } #[derive(Default)] @@ -154,7 +155,8 @@ pub struct KernelProgramInfo { #[derive(Default)] pub struct CodeSourceMap { - // Fieldless class + pub length: u32, + pub data: Vec, } #[derive(Default)] @@ -175,18 +177,33 @@ pub struct PcDescriptors { pub struct ExceptionHandlers { pub handled_types_data: u32, // ArrayPtr pub packed_fields: u32, + pub num_entries: usize, + pub entries: Vec, +} + +#[derive(Default)] +pub struct ExceptionHandlerInfo { + pub handler_pc_offset: u32, + pub outer_try_index: i16, + pub needs_stacktrace: i8, + pub has_catch_all: i8, + pub is_generated: i8, } #[derive(Default)] pub struct Context { pub parent: u32, // ContextPtr pub num_variables: i32, + pub variables: Vec, } #[derive(Default)] pub struct ContextScope { pub num_variables: i32, pub is_implicit: bool, + /// Flattened `VariableDesc` reference fields. There are ten per variable + /// in this SDK revision. + pub variables: Vec, } #[derive(Default)] @@ -215,6 +232,7 @@ pub struct TypeArguments { pub length: Smi, // SmiPtr pub hash: Smi, // SmiPtr pub nullability: Smi, // SmiPtr + pub types: Vec, // AbstractTypePtr elements } #[derive(Default)] @@ -278,6 +296,7 @@ pub struct _String { pub struct Array { pub type_arguments: u32, // TypeArgumentsPtr pub length: Smi, // SmiPtr + pub elements: Vec, // ObjectPtr elements } #[derive(Default)] @@ -313,13 +332,23 @@ pub struct Closure { #[derive(Default)] pub struct Instance { - // Fieldless class + pub next_field_offset_in_words: i32, + pub instance_size_in_words: i32, + pub unboxed_fields_bitmap: u64, + pub fields: Vec, +} + +#[derive(Debug)] +pub enum InstanceField { + Reference(u32), + Unboxed(u64), } #[derive(Default)] pub struct WeakArray { pub next_seen_by_gc: u32, // WeakArrayPtr pub length: Smi, // SmiPtr + pub elements: Vec, // ObjectPtr elements } #[derive(Default)] @@ -330,7 +359,8 @@ pub struct TypedDataBase { #[derive(Default)] pub struct TypedData { - // Fieldless class + pub length: usize, + pub data: Vec, } #[derive(Default)] @@ -457,41 +487,65 @@ pub struct WeakProperty { #[derive(Default)] pub struct Map { - // Fieldless class + pub type_arguments: u32, + pub hash_mask: u32, + pub data: u32, + pub used_data: u32, + pub deleted_keys: u32, + pub index: u32, } #[derive(Default)] pub struct Set { - // Fieldless class + pub type_arguments: u32, + pub hash_mask: u32, + pub data: u32, + pub used_data: u32, + pub deleted_keys: u32, + pub index: u32, } #[derive(Default)] pub struct Float32x4 { - // Fieldless class + pub value: Vec, } #[derive(Default)] pub struct Float64x2 { - // Fieldless class + pub value: Vec, } #[derive(Default)] pub struct ConstMap { - // Fieldless class + pub type_arguments: u32, + pub hash_mask: u32, + pub data: u32, + pub used_data: u32, + pub deleted_keys: u32, + pub index: u32, } #[derive(Default)] pub struct ConstSet { - // Fieldless class + pub type_arguments: u32, + pub hash_mask: u32, + pub data: u32, + pub used_data: u32, + pub deleted_keys: u32, + pub index: u32, } #[derive(Default)] pub struct Record { pub shape: Smi, // SmiPtr pub padding: u32, + pub num_fields: usize, + pub fields: Vec, } #[derive(Default)] pub struct ImmutableArray { - // Fieldless class + pub type_arguments: u32, + pub length: Smi, + pub elements: Vec, } diff --git a/crates/flutterdec-serwalker/src/snapshot.rs b/crates/flutterdec-serwalker/src/snapshot.rs index 4ea8897..bcec701 100644 --- a/crates/flutterdec-serwalker/src/snapshot.rs +++ b/crates/flutterdec-serwalker/src/snapshot.rs @@ -1,7 +1,9 @@ use std::collections::HashMap; +use std::mem::size_of; use crate::cluster::{decide_cluster, Cluster}; -use crate::constants::{self, ClassId, MAGIC_BYTES, UNSIGNED_M}; +use crate::constants::{self, ClassId, HEADER_SIZE, MAGIC_BYTES, UNSIGNED_M}; +use crate::instruction_table::{parse_instr_table_from_rodata, InstructionTable}; use crate::program_roots::structs::ProgramRoots; use crate::program_roots::{parse_dispatch_table, parse_field_table, parse_object_store}; use crate::stream::Stream; @@ -41,6 +43,7 @@ pub struct DataSnapshot { clusters: HashMap>, cluster_order: Vec, // used in the fill step to know which cluster's read_fill function to call roots: ProgramRoots, + instruction_table: InstructionTable, magic_bytes: u32, size: u64, @@ -107,6 +110,12 @@ impl DataSnapshot { anyhow::anyhow!("Couldn't find cluster implementation for class {:?}", cid) })?; + cluster.set_metadata( + tags, + cid, + decoded_tags.is_deeply_immutable(), + decoded_tags.is_canonical(), + ); cluster.read_alloc(&mut curr_ref_id, stream)?; // Composite key exactly as suggested to PR reviewer @@ -151,6 +160,11 @@ impl DataSnapshot { ); Ok(()) } + + fn parse_instruction_table(&mut self, stream: &mut Stream) -> anyhow::Result<()> { + self.instruction_table = parse_instr_table_from_rodata(stream)?; + Ok(()) + } } pub fn parse_snapshot(stream: &mut Stream) -> anyhow::Result { @@ -159,7 +173,14 @@ pub fn parse_snapshot(stream: &mut Stream) -> anyhow::Result { println!("Now parsing the snapshot..."); snapshot.parse_header(stream)?; snapshot.parse_clusters(stream)?; - snapshot.parse_roots(stream)?; + snapshot.parse_roots(stream)?; // right after we finish reading the roots we need to align + + stream.align_stream(HEADER_SIZE)?; + + // after that, we land right at the start of the ROData image + // where the instruction table is, at an offset we already know + stream.seek(stream.get_current_pos() + snapshot.instr_table_offset)?; + snapshot.parse_instruction_table(stream)?; Ok(snapshot) } diff --git a/crates/flutterdec-serwalker/src/stream.rs b/crates/flutterdec-serwalker/src/stream.rs index e7015dd..f1c7e2e 100644 --- a/crates/flutterdec-serwalker/src/stream.rs +++ b/crates/flutterdec-serwalker/src/stream.rs @@ -2,13 +2,13 @@ use anyhow::{anyhow, bail, Result}; use crate::constants::{DATA_BITS_PER_BYTE, SIGNED_M, UNSIGNED_M, UNSIGNED_MAX_DATA_PER_BYTE}; -pub struct Stream { - byte_stream: &[u8], +pub struct Stream<'a> { + byte_stream: &'a [u8], curr_stream_offset: usize, } -impl Stream { - pub fn new(byte_stream: &[u8]) -> Self { +impl<'a> Stream<'a> { + pub fn new(byte_stream: &'a [u8]) -> Self { Self { byte_stream, curr_stream_offset: 0, @@ -26,6 +26,18 @@ impl Stream { Ok(()) } + pub fn align_stream(&mut self, alignment: usize) -> anyhow::Result<()> { + let mut next_pos = self.get_current_pos(); + if next_pos % alignment == 0 { + return Ok(()); + } + + next_pos = next_pos & !(alignment - 1); + next_pos += alignment; + + self.seek(next_pos) + } + fn take(&mut self, n: usize) -> Result<&[u8]> { let end = self .curr_stream_offset diff --git a/crates/flutterdec-serwalker/src/utils.rs b/crates/flutterdec-serwalker/src/utils.rs index 0481581..6ecf90d 100644 --- a/crates/flutterdec-serwalker/src/utils.rs +++ b/crates/flutterdec-serwalker/src/utils.rs @@ -6,6 +6,8 @@ macro_rules! DECLARE_FIXED_LENGTH_CLUSTER { pub struct $cluster_name { tags: u32, cid: ClassId, + is_deeply_immutable: bool, + is_canonical: bool, obj_count: u64, start_of_fill: usize, @@ -20,6 +22,19 @@ macro_rules! DECLARE_FIXED_LENGTH_CLUSTER { } impl Cluster for $cluster_name { + fn set_metadata( + &mut self, + tags: u32, + cid: ClassId, + is_deeply_immutable: bool, + is_canonical: bool, + ) { + self.tags = tags; + self.cid = cid; + self.is_deeply_immutable = is_deeply_immutable; + self.is_canonical = is_canonical; + } + fn read_alloc( &mut self, last_ref_id: &mut u64, @@ -66,6 +81,8 @@ macro_rules! DECLARE_VARIABLE_LENGTH_CLUSTER { pub struct $cluster_name { tags: u32, cid: ClassId, + is_deeply_immutable: bool, + is_canonical: bool, obj_count: u64, start_of_fill: usize, @@ -125,8 +142,6 @@ macro_rules! DECODE_IS_CANONICAL { }; } -/// Bit layout matches UntaggedObject::TagBits in raw_object.h: -/// ClassIdTag at 12 (20 bits), CanonicalBit at 1, ImmutableBit at 7. pub fn decode_tags(tags: u32) -> anyhow::Result { let class_id = DECODE_CID!(tags).map_err(|_| { anyhow::anyhow!( From c7c567b67771542c4d58c5658e2e51631951aadb Mon Sep 17 00:00:00 2001 From: kalixtez Date: Mon, 3 Aug 2026 18:29:41 -0500 Subject: [PATCH 50/50] fix(serwalker)!: adapted current code to version 3.11.1, which was the closest tag to the commit serwalker was being based on, minor differences between this tagged commit and the initial base commit were corrected (i.e, a RegEx object field, a bit in the Object tags now called immutable instead of deeply_immutable), finally compilation errors were fixed --- crates/flutterdec-adapter/src/lib.rs | 10 +++++-- .../flutterdec-serwalker/src/cluster/mod.rs | 15 ++++------ crates/flutterdec-serwalker/src/constants.rs | 5 ++++ .../src/instruction_table.rs | 21 ++++++++++++-- crates/flutterdec-serwalker/src/lib.rs | 21 ++++++++++---- .../src/program_roots/structs.rs | 8 +++--- .../src/raw_object/mod.rs | 2 +- crates/flutterdec-serwalker/src/snapshot.rs | 28 +++++++++++++++++-- crates/flutterdec-serwalker/src/utils.rs | 24 ++++++++-------- 9 files changed, 94 insertions(+), 40 deletions(-) diff --git a/crates/flutterdec-adapter/src/lib.rs b/crates/flutterdec-adapter/src/lib.rs index 76f1e63..31630f3 100644 --- a/crates/flutterdec-adapter/src/lib.rs +++ b/crates/flutterdec-adapter/src/lib.rs @@ -2,6 +2,7 @@ use anyhow::{anyhow, bail, Context, Result}; use serde::{Deserialize, Serialize}; use std::fs; use std::io::Write; +#[cfg(unix)] use std::os::unix::fs::PermissionsExt; use std::path::{Path, PathBuf}; use std::process::Command; @@ -171,9 +172,12 @@ pub fn install_adapter(repo_root: &Path, dart_hash: &str) -> Result { ); fs::write(&out, script).with_context(|| format!("write adapter script: {}", out.display()))?; - let mut perms = fs::metadata(&out)?.permissions(); - perms.set_mode(0o755); - fs::set_permissions(&out, perms)?; + #[cfg(unix)] + { + let mut perms = fs::metadata(&out)?.permissions(); + perms.set_mode(0o755); + fs::set_permissions(&out, perms)?; + } Ok(out) } diff --git a/crates/flutterdec-serwalker/src/cluster/mod.rs b/crates/flutterdec-serwalker/src/cluster/mod.rs index 51aedcf..c0cc69e 100644 --- a/crates/flutterdec-serwalker/src/cluster/mod.rs +++ b/crates/flutterdec-serwalker/src/cluster/mod.rs @@ -6,13 +6,7 @@ use crate::DECLARE_VARIABLE_LENGTH_CLUSTER; use crate::FFI_TYPES_LIST; pub trait Cluster { - fn set_metadata( - &mut self, - tags: u32, - cid: ClassId, - is_deeply_immutable: bool, - is_canonical: bool, - ); + fn set_metadata(&mut self, tags: u32, cid: ClassId, is_immutable: bool, is_canonical: bool); fn is_fixed_len(&self) -> bool; fn read_alloc(&mut self, last_ref_id: &mut u64, stream: &mut Stream) -> anyhow::Result; fn read_fill(&mut self, stream: &mut Stream) -> anyhow::Result; @@ -371,7 +365,8 @@ DECLARE_FIXED_LENGTH_CLUSTER!(RegExp, RegExpCluster, |_self, stream| { obj.two_byte_sticky = stream.read_ref_id()?; obj.num_one_byte_registers = stream.read()? as i32; obj.num_two_byte_registers = stream.read()? as i32; - obj.flags = stream.read_unsigned()? as u32; + // Deserializer::Read() dispatches to ReadStream::Raw<1, T>. + obj.type_flags = stream.read_byte()? as i8; } }); DECLARE_FIXED_LENGTH_CLUSTER!(WeakProperty, WeakPropertyCluster, |_self, stream| { @@ -397,12 +392,12 @@ macro_rules! IMPLEMENT_VARIABLE_LENGTH_CLUSTER { &mut self, tags: u32, cid: ClassId, - is_deeply_immutable: bool, + is_immutable: bool, is_canonical: bool, ) { self.tags = tags; self.cid = cid; - self.is_deeply_immutable = is_deeply_immutable; + self.is_immutable = is_immutable; self.is_canonical = is_canonical; } diff --git a/crates/flutterdec-serwalker/src/constants.rs b/crates/flutterdec-serwalker/src/constants.rs index 76f9104..adb4137 100644 --- a/crates/flutterdec-serwalker/src/constants.rs +++ b/crates/flutterdec-serwalker/src/constants.rs @@ -26,6 +26,11 @@ pub const SMI_SHIFT: usize = 1usize; pub const VERSION_HASH_LENGTH: usize = 32usize; +// Version::SnapshotString() for the Dart SDK 3.11.1 tag (e927f58e327a). +// The current parser has a hardcoded VM schema, so reject other layouts until +// schema information can be supplied dynamically. +pub const DART_3_11_1_SNAPSHOT_HASH: &str = "78da37fed6bf1489361a312568249f3f"; + pub const HEADER_SIZE: usize = 64; macro_rules! DEFINE_CLASS_ID { diff --git a/crates/flutterdec-serwalker/src/instruction_table.rs b/crates/flutterdec-serwalker/src/instruction_table.rs index 8503dd8..a8986c7 100644 --- a/crates/flutterdec-serwalker/src/instruction_table.rs +++ b/crates/flutterdec-serwalker/src/instruction_table.rs @@ -1,4 +1,4 @@ -use crate::{instruction_table, stream::Stream}; +use crate::stream::Stream; #[derive(Default)] pub struct InstructionTable @@ -20,8 +20,8 @@ struct DataEntry { pub fn parse_instr_table_from_rodata(stream: &mut Stream) -> anyhow::Result { // ROData objects are wrapped inside OneByteString objects, so we need to read the syntetic fields first. - let mut tags = stream.read_raw_u64()?; - let mut data_byte_size = stream.read_raw_u64()?; + let _tags = stream.read_raw_u64()?; + let _data_byte_size = stream.read_raw_u64()?; // i wont do anything with them for now, for a normal snapshot, the tags should be a OneByteString cid // and data_byte_size should be the size of the contained InstructionsTable::Data // i.e 4 * sizeof(u32) + (2*sizeof(u32)) * length @@ -49,3 +49,18 @@ pub fn parse_instr_table_from_rodata(stream: &mut Stream) -> anyhow::Result anyhow::Result { + let abs_index = instr_table.first_entry_with_code + code_index as usize; + let entry = instr_table.data.get(abs_index).ok_or_else(|| { + anyhow::anyhow!( + "instruction-table index {abs_index} is out of bounds for length {}", + instr_table.data.len() + ) + })?; + + Ok(entry.pc_offset) +} diff --git a/crates/flutterdec-serwalker/src/lib.rs b/crates/flutterdec-serwalker/src/lib.rs index 4bbf70a..7a79fbc 100644 --- a/crates/flutterdec-serwalker/src/lib.rs +++ b/crates/flutterdec-serwalker/src/lib.rs @@ -14,7 +14,8 @@ mod stream; use flutterdec_adapter::{AdapterInput, ProgramModel}; use crate::{ - info_producer::{produce_model_headers, produce_model_object_info}, + constants::DART_3_11_1_SNAPSHOT_HASH, + info_producer::{produce_model_headers, produce_model_object_info, produce_model_object_pool}, snapshot::parse_snapshot, stream::Stream, }; @@ -22,14 +23,24 @@ use crate::{ pub fn walk_snapshot_and_produce_model( adapter_input: &AdapterInput, ) -> anyhow::Result { - let program_model: ProgramModel; + let mut program_model = ProgramModel { + schema_version: 3, + adapter_kind: "serwalker".to_owned(), + dart_version: "3.11.1".to_owned(), + snapshot_hash: DART_3_11_1_SNAPSHOT_HASH.to_owned(), + arch: "unknown".to_owned(), + libraries: Vec::new(), + classes: Vec::new(), + functions: Vec::new(), + object_pool: Vec::new(), + }; let mut isolate_data_stream = Stream::new(adapter_input.isolate_data); let isolate_data_snapshot = parse_snapshot(&mut isolate_data_stream)?; - produce_model_headers(&mut program_model, &isolate_data_snapshot); - produce_model_object_info(&mut program_model, &isolate_data_snapshot); - produce_model_object_pool(&mut program_model, &isolate_data_snapshot); + produce_model_headers(&mut program_model, &isolate_data_snapshot)?; + produce_model_object_info(&mut program_model, &isolate_data_snapshot)?; + produce_model_object_pool(&mut program_model, &isolate_data_snapshot)?; Ok(program_model) } diff --git a/crates/flutterdec-serwalker/src/program_roots/structs.rs b/crates/flutterdec-serwalker/src/program_roots/structs.rs index 032f250..6e7ea07 100644 --- a/crates/flutterdec-serwalker/src/program_roots/structs.rs +++ b/crates/flutterdec-serwalker/src/program_roots/structs.rs @@ -3,7 +3,7 @@ use crate::stream::Stream; macro_rules! object_store_aot_fields { ($emit:ident) => { $emit! { - // Layout for the 3.12 Dart version, and as early as 3.11 + // Layout for the 3.11.X Dart version, and as early as 3.11.0 list_class: u32, // ClassPtr map_class: u32, // ClassPtr set_class: u32, // ClassPtr @@ -254,7 +254,7 @@ macro_rules! define_object_store { ($( $field:ident: u32,)*) => { #[derive(Default)] #[repr(C)] - pub(super) struct ObjectStore { + pub(crate) struct ObjectStore { $( $field: u32, )* } @@ -282,7 +282,7 @@ pub(super) struct IsolateObjectStore error_listeners: u32, // GrowableObjectArrayPtr } #[derive(Default)] -pub(super) struct FieldTable { +pub(crate) struct FieldTable { pub(super) length: usize, pub(super) field_refs: Vec, } @@ -294,7 +294,7 @@ pub(super) enum DispatchTableEntry { } #[derive(Debug, Default)] -pub(super) struct DispatchTable { +pub(crate) struct DispatchTable { pub(super) first_code_ref: Option, pub(super) entries: Vec, } diff --git a/crates/flutterdec-serwalker/src/raw_object/mod.rs b/crates/flutterdec-serwalker/src/raw_object/mod.rs index 5939efa..b61e737 100644 --- a/crates/flutterdec-serwalker/src/raw_object/mod.rs +++ b/crates/flutterdec-serwalker/src/raw_object/mod.rs @@ -475,7 +475,7 @@ pub struct RegExp { pub two_byte_sticky: u32, // TypedDataPtr pub num_one_byte_registers: i32, pub num_two_byte_registers: i32, - pub flags: u32, + pub type_flags: i8, } #[derive(Default)] diff --git a/crates/flutterdec-serwalker/src/snapshot.rs b/crates/flutterdec-serwalker/src/snapshot.rs index bcec701..c64bb54 100644 --- a/crates/flutterdec-serwalker/src/snapshot.rs +++ b/crates/flutterdec-serwalker/src/snapshot.rs @@ -2,7 +2,9 @@ use std::collections::HashMap; use std::mem::size_of; use crate::cluster::{decide_cluster, Cluster}; -use crate::constants::{self, ClassId, HEADER_SIZE, MAGIC_BYTES, UNSIGNED_M}; +use crate::constants::{ + self, ClassId, DART_3_11_1_SNAPSHOT_HASH, HEADER_SIZE, MAGIC_BYTES, UNSIGNED_M, +}; use crate::instruction_table::{parse_instr_table_from_rodata, InstructionTable}; use crate::program_roots::structs::ProgramRoots; use crate::program_roots::{parse_dispatch_table, parse_field_table, parse_object_store}; @@ -86,8 +88,28 @@ impl DataSnapshot { self.kind = SnapshotKind::try_from(stream.read_raw_u64()?).map_err(|e| anyhow::anyhow!(e))?; + if !matches!(&self.kind, SnapshotKind::FullAOT) { + anyhow::bail!("Serwalker currently supports FullAOT snapshots only"); + } + self.parse_version_and_features(stream)?; + if self.version_hash != DART_3_11_1_SNAPSHOT_HASH { + anyhow::bail!( + "unsupported Dart snapshot hash {}; expected {} (Dart 3.11.1)", + self.version_hash, + DART_3_11_1_SNAPSHOT_HASH + ); + } + + if !self + .features + .split_ascii_whitespace() + .any(|feature| feature == "compressed-pointers") + { + anyhow::bail!("Serwalker currently requires a compressed-pointers snapshot"); + } + self.num_base_objects = stream.read_unsigned()?; self.num_objects = stream.read_unsigned()?; self.num_clusters = stream.read_unsigned()?; @@ -113,7 +135,7 @@ impl DataSnapshot { cluster.set_metadata( tags, cid, - decoded_tags.is_deeply_immutable(), + decoded_tags.is_immutable(), decoded_tags.is_canonical(), ); cluster.read_alloc(&mut curr_ref_id, stream)?; @@ -121,7 +143,7 @@ impl DataSnapshot { // Composite key exactly as suggested to PR reviewer let key = (cid as u32) << 2 | ((decoded_tags.is_canonical() as u32) << 1) - | (decoded_tags.is_deeply_immutable() as u32); + | (decoded_tags.is_immutable() as u32); self.clusters.insert(key, cluster); self.cluster_order.push(key); } diff --git a/crates/flutterdec-serwalker/src/utils.rs b/crates/flutterdec-serwalker/src/utils.rs index 6ecf90d..7a36584 100644 --- a/crates/flutterdec-serwalker/src/utils.rs +++ b/crates/flutterdec-serwalker/src/utils.rs @@ -6,7 +6,7 @@ macro_rules! DECLARE_FIXED_LENGTH_CLUSTER { pub struct $cluster_name { tags: u32, cid: ClassId, - is_deeply_immutable: bool, + is_immutable: bool, is_canonical: bool, obj_count: u64, @@ -26,12 +26,12 @@ macro_rules! DECLARE_FIXED_LENGTH_CLUSTER { &mut self, tags: u32, cid: ClassId, - is_deeply_immutable: bool, + is_immutable: bool, is_canonical: bool, ) { self.tags = tags; self.cid = cid; - self.is_deeply_immutable = is_deeply_immutable; + self.is_immutable = is_immutable; self.is_canonical = is_canonical; } @@ -81,7 +81,7 @@ macro_rules! DECLARE_VARIABLE_LENGTH_CLUSTER { pub struct $cluster_name { tags: u32, cid: ClassId, - is_deeply_immutable: bool, + is_immutable: bool, is_canonical: bool, obj_count: u64, @@ -100,7 +100,7 @@ macro_rules! DECLARE_VARIABLE_LENGTH_CLUSTER { pub struct DecodedTags { class_id: ClassId, - is_deeply_immutable: bool, + is_immutable: bool, is_canonical: bool, } @@ -108,7 +108,7 @@ impl DecodedTags { pub fn new(cid: ClassId, immut: bool, canonical: bool) -> Self { Self { class_id: cid, - is_deeply_immutable: immut, + is_immutable: immut, is_canonical: canonical, } } @@ -117,8 +117,8 @@ impl DecodedTags { self.class_id } - pub fn is_deeply_immutable(&self) -> bool { - self.is_deeply_immutable + pub fn is_immutable(&self) -> bool { + self.is_immutable } pub fn is_canonical(&self) -> bool { @@ -131,9 +131,11 @@ macro_rules! DECODE_CID { ClassId::try_from(($tags >> 12) & 0xFFFFF) }; } -macro_rules! DECODE_IS_DEEPLY_IMMUTABLE { +macro_rules! DECODE_IS_IMMUTABLE { ($tags:expr) => { - (($tags >> 7) & 0x1) == 1 + // Dart 3.11.1 UntaggedObject::ImmutableBit is bit 6. Mainline later + // split this into ShallowImmutableBit (6) and DeeplyImmutableBit (7). + (($tags >> 6) & 0x1) == 1 }; } macro_rules! DECODE_IS_CANONICAL { @@ -151,7 +153,7 @@ pub fn decode_tags(tags: u32) -> anyhow::Result { })?; Ok(DecodedTags::new( class_id, - DECODE_IS_DEEPLY_IMMUTABLE!(tags), + DECODE_IS_IMMUTABLE!(tags), DECODE_IS_CANONICAL!(tags), )) }