symbolic_il2cpp/line_mapping/
mod.rs1mod from_object;
2
3use indexmap::IndexSet;
4use std::collections::HashMap;
5
6pub use from_object::ObjectLineMapping;
7
8#[derive(Debug)]
10struct LineEntry {
11 cpp_line: u32,
13 cs_line: u32,
15 cs_file_idx: usize,
17}
18
19#[derive(Debug, Default)]
21pub struct LineMapping {
22 cs_files: IndexSet<String>,
24 cpp_file_map: HashMap<String, Vec<LineEntry>>,
26}
27
28impl LineMapping {
29 pub fn parse(data: &[u8]) -> Option<Self> {
33 let json: serde_json::Value = serde_json::from_slice(data).ok()?;
34 let mut result = Self::default();
35
36 if let serde_json::Value::Object(object) = json {
37 for (cpp_file, file_map) in object {
38 if cpp_file == "__debug-id__" {
42 continue;
43 }
44 let mut lines = Vec::new();
45 if let serde_json::Value::Object(file_map) = file_map {
46 for (cs_file, line_map) in file_map {
47 if let serde_json::Value::Object(line_map) = line_map {
48 let cs_file_idx = result.cs_files.insert_full(cs_file).0;
49 for (from, to) in line_map {
50 let cpp_line = from.parse().ok()?;
51 let cs_line = to.as_u64().and_then(|n| n.try_into().ok())?;
52 lines.push(LineEntry {
53 cpp_line,
54 cs_line,
55 cs_file_idx,
56 });
57 }
58 }
59 }
60 }
61 lines.sort_by_key(|entry| entry.cpp_line);
62 result.cpp_file_map.insert(cpp_file, lines);
63 }
64 }
65
66 Some(result)
67 }
68
69 pub fn lookup(&self, file: &str, line: u32) -> Option<(&str, u32)> {
73 let lines = self.cpp_file_map.get(file)?;
74
75 let idx = match lines.binary_search_by_key(&line, |entry| entry.cpp_line) {
76 Ok(idx) => idx,
77 Err(0) => return None,
78 Err(idx) => idx - 1,
79 };
80
81 let entry = lines.get(idx)?;
82
83 if line.saturating_sub(entry.cpp_line) > 50 {
85 None
86 } else {
87 Some((self.cs_files.get_index(entry.cs_file_idx)?, entry.cs_line))
88 }
89 }
90}
91
92#[cfg(test)]
93mod tests {
94 use super::*;
95
96 fn source_to_mapping(cpp_source: &[u8]) -> LineMapping {
97 let mapping =
98 HashMap::from([("main.cpp", ObjectLineMapping::parse_source_file(cpp_source))]);
99 let mapping_json = serde_json::to_string(&mapping).unwrap();
100
101 LineMapping::parse(mapping_json.as_bytes()).unwrap()
102 }
103
104 #[test]
105 fn test_lookup() {
106 let parsed_mapping = source_to_mapping(
107 b"Lorem ipsum dolor sit amet
108 //<source_info:main.cs:17>
109 // some
110 // comments
111 some expression // 5
112 stretching
113 over
114 multiple lines
115
116 // blank lines
117
118 // and stuff
119 // 13
120 //<source_info:main.cs:29>
121 actual source code // 15
122 ",
123 );
124 assert_eq!(parsed_mapping.lookup("main.cpp", 0), None);
125 assert_eq!(parsed_mapping.lookup("main.cpp", 1), None);
126 assert_eq!(parsed_mapping.lookup("main.cpp", 2), None);
127 assert_eq!(parsed_mapping.lookup("main.cpp", 3), None);
128 assert_eq!(parsed_mapping.lookup("main.cpp", 4), None);
129 assert_eq!(parsed_mapping.lookup("main.cpp", 5), Some(("main.cs", 17)));
130 assert_eq!(parsed_mapping.lookup("main.cpp", 6), Some(("main.cs", 17)));
131 assert_eq!(parsed_mapping.lookup("main.cpp", 7), Some(("main.cs", 17)));
132 assert_eq!(parsed_mapping.lookup("main.cpp", 8), Some(("main.cs", 17)));
133 assert_eq!(parsed_mapping.lookup("main.cpp", 9), Some(("main.cs", 17)));
134 assert_eq!(parsed_mapping.lookup("main.cpp", 10), Some(("main.cs", 17)));
135 assert_eq!(parsed_mapping.lookup("main.cpp", 11), Some(("main.cs", 17)));
136 assert_eq!(parsed_mapping.lookup("main.cpp", 12), Some(("main.cs", 17)));
137 assert_eq!(parsed_mapping.lookup("main.cpp", 13), Some(("main.cs", 17)));
138 assert_eq!(parsed_mapping.lookup("main.cpp", 14), Some(("main.cs", 17)));
139 assert_eq!(parsed_mapping.lookup("main.cpp", 15), Some(("main.cs", 29)));
140 assert_eq!(parsed_mapping.lookup("main.cpp", 16), Some(("main.cs", 29)));
141 }
142
143 #[test]
144 fn test_lookup_real_code() {
145 let cpp_source = r#"
148IL_001f: |
149 { |
150 il2cpp_codegen_runtime_class_init_inline(SentrySdk_t74D2EF9D77AF1E |
151 SentrySdk_ConfigureScope_m365F1871733F0C48E4B585067AC55DD27E654BC4 |
152 //<source_info:/Scripts/AdditionalButtons.cs:20> |
153 // Debug.Log("User set: ant"); |
154 il2cpp_codegen_runtime_class_init_inline(Debug_t8394C7EEAECA3689C2 | /Scripts/AdditionalButtons.cs:20
155 Debug_Log_m87A9A3C761FF5C43ED8A53B16190A53D08F818BB(_stringLiteral | /Scripts/AdditionalButtons.cs:20
156 //<source_info:/Scripts/AdditionalButtons.cs:21> | /Scripts/AdditionalButtons.cs:20
157 // } | /Scripts/AdditionalButtons.cs:20
158 return; | /Scripts/AdditionalButtons.cs:21
159 } | /Scripts/AdditionalButtons.cs:21
160} | /Scripts/AdditionalButtons.cs:21
161// System.Void AdditionalButtons::CaptureMessageWithContext() | /Scripts/AdditionalButtons.cs:21
162IL2CPP_EXTERN_C IL2CPP_METHOD_ATTR void AdditionalButtons_CaptureMessageWi | /Scripts/AdditionalButtons.cs:21
163{ | /Scripts/AdditionalButtons.cs:21
164 static bool s_Il2CppMethodInitialized; | /Scripts/AdditionalButtons.cs:21
165 if (!s_Il2CppMethodInitialized) | /Scripts/AdditionalButtons.cs:21
166 { | /Scripts/AdditionalButtons.cs:21
167 il2cpp_codegen_initialize_runtime_metadata((uintptr_t*)&Action_1_t | /Scripts/AdditionalButtons.cs:21
168 il2cpp_codegen_initialize_runtime_metadata((uintptr_t*)&SentrySdk_ | /Scripts/AdditionalButtons.cs:21
169 il2cpp_codegen_initialize_runtime_metadata((uintptr_t*)&U3CU3Ec_U3 | /Scripts/AdditionalButtons.cs:21
170 il2cpp_codegen_initialize_runtime_metadata((uintptr_t*)&U3CU3Ec_U3 | /Scripts/AdditionalButtons.cs:21
171 il2cpp_codegen_initialize_runtime_metadata((uintptr_t*)&U3CU3Ec_t7 | /Scripts/AdditionalButtons.cs:21
172 il2cpp_codegen_initialize_runtime_metadata((uintptr_t*)&_stringLit | /Scripts/AdditionalButtons.cs:21
173 s_Il2CppMethodInitialized = true; | /Scripts/AdditionalButtons.cs:21
174 } | /Scripts/AdditionalButtons.cs:21
175 Action_1_t1C1C5545B1F9348689F2EE7364FC9ACC425526EC* G_B2_0 = NULL; | /Scripts/AdditionalButtons.cs:21
176 Action_1_t1C1C5545B1F9348689F2EE7364FC9ACC425526EC* G_B1_0 = NULL; | /Scripts/AdditionalButtons.cs:21
177 Action_1_t1C1C5545B1F9348689F2EE7364FC9ACC425526EC* G_B4_0 = NULL; | /Scripts/AdditionalButtons.cs:21
178 Action_1_t1C1C5545B1F9348689F2EE7364FC9ACC425526EC* G_B3_0 = NULL; | /Scripts/AdditionalButtons.cs:21
179 { | /Scripts/AdditionalButtons.cs:21
180 //<source_info:/Scripts/AdditionalButtons.cs:32> | /Scripts/AdditionalButtons.cs:21
181 // SentrySdk.ConfigureScope(scope => | /Scripts/AdditionalButtons.cs:32
182 //<source_info:/Scripts/AdditionalButtons.cs:33> | /Scripts/AdditionalButtons.cs:32
183 // { | /Scripts/AdditionalButtons.cs:33
184 //<source_info:/Scripts/AdditionalButtons.cs:34> | /Scripts/AdditionalButtons.cs:33
185 // scope.Contexts["character"] = new PlayerCharacter | /Scripts/AdditionalButtons.cs:34
186 //<source_info:/Scripts/AdditionalButtons.cs:35> | /Scripts/AdditionalButtons.cs:34
187 // { | /Scripts/AdditionalButtons.cs:35
188 //<source_info:/Scripts/AdditionalButtons.cs:36> | /Scripts/AdditionalButtons.cs:35
189 // Name = "Mighty Fighter", | /Scripts/AdditionalButtons.cs:36
190 //<source_info:/Scripts/AdditionalButtons.cs:37> | /Scripts/AdditionalButtons.cs:36
191 // Age = 19, | /Scripts/AdditionalButtons.cs:37
192 //<source_info:/Scripts/AdditionalButtons.cs:38> | /Scripts/AdditionalButtons.cs:37
193 // AttackType = "melee" | /Scripts/AdditionalButtons.cs:38
194 //<source_info:/Scripts/AdditionalButtons.cs:39> | /Scripts/AdditionalButtons.cs:38
195 // }; | /Scripts/AdditionalButtons.cs:39
196 //<source_info:/Scripts/AdditionalButtons.cs:40> | /Scripts/AdditionalButtons.cs:39
197 // }); | /Scripts/AdditionalButtons.cs:39
198 il2cpp_codegen_runtime_class_init_inline(U3CU3Ec_t79DD2293FFBDE9C9 | /Scripts/AdditionalButtons.cs:40
199 Action_1_t1C1C5545B1F9348689F2EE7364FC9ACC425526EC* L_0 = ((U3CU3E | /Scripts/AdditionalButtons.cs:40
200 Action_1_t1C1C5545B1F9348689F2EE7364FC9ACC425526EC* L_1 = L_0; | /Scripts/AdditionalButtons.cs:40
201 G_B1_0 = L_1; | /Scripts/AdditionalButtons.cs:40
202 if (L_1) | /Scripts/AdditionalButtons.cs:40
203 { | /Scripts/AdditionalButtons.cs:40
204 G_B2_0 = L_1; | /Scripts/AdditionalButtons.cs:40
205 goto IL_001f; | /Scripts/AdditionalButtons.cs:40
206 } | /Scripts/AdditionalButtons.cs:40
207 } | /Scripts/AdditionalButtons.cs:40
208 { | /Scripts/AdditionalButtons.cs:40
209 il2cpp_codegen_runtime_class_init_inline(U3CU3Ec_t79DD2293FFBDE9C9 | /Scripts/AdditionalButtons.cs:40
210 U3CU3Ec_t79DD2293FFBDE9C9C239DB7D77EA41E31CFCA564* L_2 = ((U3CU3Ec | /Scripts/AdditionalButtons.cs:40
211 Action_1_t1C1C5545B1F9348689F2EE7364FC9ACC425526EC* L_3 = (Action_ | /Scripts/AdditionalButtons.cs:40
212 NullCheck(L_3); | /Scripts/AdditionalButtons.cs:40
213 Action_1__ctor_mCE58979C800E29B3B4B1673D24BDF60161B4A942(L_3, L_2, | /Scripts/AdditionalButtons.cs:40
214 Action_1_t1C1C5545B1F9348689F2EE7364FC9ACC425526EC* L_4 = L_3; | /Scripts/AdditionalButtons.cs:40
215 ((U3CU3Ec_t79DD2293FFBDE9C9C239DB7D77EA41E31CFCA564_StaticFields*) | /Scripts/AdditionalButtons.cs:40
216 Il2CppCodeGenWriteBarrier((void**)(&((U3CU3Ec_t79DD2293FFBDE9C9C23 | /Scripts/AdditionalButtons.cs:40
217 G_B2_0 = L_4; | /Scripts/AdditionalButtons.cs:40
218 } | /Scripts/AdditionalButtons.cs:40
219 | /Scripts/AdditionalButtons.cs:40
220IL_001f: | /Scripts/AdditionalButtons.cs:40
221 { | /Scripts/AdditionalButtons.cs:40
222 il2cpp_codegen_runtime_class_init_inline(SentrySdk_t74D2EF9D77AF1E | /Scripts/AdditionalButtons.cs:40
223 SentrySdk_ConfigureScope_m365F1871733F0C48E4B585067AC55DD27E654BC4 | /Scripts/AdditionalButtons.cs:40
224 //<source_info:/Scripts/AdditionalButtons.cs:42> | /Scripts/AdditionalButtons.cs:40
225 // SentrySdk.CaptureMessage("Capturing with player character conte | /Scripts/AdditionalButtons.cs:40
226 SentryId_t0035EA63F72CBEBE7C342CD0C3A1D80C91A77940 L_5; | /Scripts/AdditionalButtons.cs:42
227 L_5 = SentrySdk_CaptureMessage_mEB25F3DA889DE8DC87DB25178C01ADE78F | /Scripts/AdditionalButtons.cs:42
228 //<source_info:/Scripts/AdditionalButtons.cs:43> | /Scripts/AdditionalButtons.cs:42
229 // SentrySdk.ConfigureScope(scope => scope.Contexts = null); | /Scripts/AdditionalButtons.cs:42
230 il2cpp_codegen_runtime_class_init_inline(U3CU3Ec_t79DD2293FFBDE9C9 | /Scripts/AdditionalButtons.cs:43
231 Action_1_t1C1C5545B1F9348689F2EE7364FC9ACC425526EC* L_6 = ((U3CU3E | /Scripts/AdditionalButtons.cs:43
232 Action_1_t1C1C5545B1F9348689F2EE7364FC9ACC425526EC* L_7 = L_6; | /Scripts/AdditionalButtons.cs:43
233 G_B3_0 = L_7; | /Scripts/AdditionalButtons.cs:43
234 if (L_7) | /Scripts/AdditionalButtons.cs:43
235 { | /Scripts/AdditionalButtons.cs:43
236 G_B4_0 = L_7; | /Scripts/AdditionalButtons.cs:43
237 goto IL_004f; | /Scripts/AdditionalButtons.cs:43
238 } | /Scripts/AdditionalButtons.cs:43
239 } | /Scripts/AdditionalButtons.cs:43
240 { | /Scripts/AdditionalButtons.cs:43
241 il2cpp_codegen_runtime_class_init_inline(U3CU3Ec_t79DD2293FFBDE9C9 | /Scripts/AdditionalButtons.cs:43
242 U3CU3Ec_t79DD2293FFBDE9C9C239DB7D77EA41E31CFCA564* L_8 = ((U3CU3Ec | /Scripts/AdditionalButtons.cs:43
243 Action_1_t1C1C5545B1F9348689F2EE7364FC9ACC425526EC* L_9 = (Action_ | /Scripts/AdditionalButtons.cs:43
244 NullCheck(L_9); | /Scripts/AdditionalButtons.cs:43
245 Action_1__ctor_mCE58979C800E29B3B4B1673D24BDF60161B4A942(L_9, L_8, | /Scripts/AdditionalButtons.cs:43
246 Action_1_t1C1C5545B1F9348689F2EE7364FC9ACC425526EC* L_10 = L_9; | /Scripts/AdditionalButtons.cs:43
247 ((U3CU3Ec_t79DD2293FFBDE9C9C239DB7D77EA41E31CFCA564_StaticFields*) | /Scripts/AdditionalButtons.cs:43
248 Il2CppCodeGenWriteBarrier((void**)(&((U3CU3Ec_t79DD2293FFBDE9C9C23 | /Scripts/AdditionalButtons.cs:43
249 G_B4_0 = L_10; | /Scripts/AdditionalButtons.cs:43
250 } | /Scripts/AdditionalButtons.cs:43
251 | /Scripts/AdditionalButtons.cs:43
252IL_004f: | /Scripts/AdditionalButtons.cs:43
253 { | /Scripts/AdditionalButtons.cs:43
254 il2cpp_codegen_runtime_class_init_inline(SentrySdk_t74D2EF9D77AF1E | /Scripts/AdditionalButtons.cs:43
255 SentrySdk_ConfigureScope_m365F1871733F0C48E4B585067AC55DD27E654BC4 | /Scripts/AdditionalButtons.cs:43
256 //<source_info:/Scripts/AdditionalButtons.cs:44> | /Scripts/AdditionalButtons.cs:43
257 // } | /Scripts/AdditionalButtons.cs:43
258 return; | /Scripts/AdditionalButtons.cs:44
259 } | /Scripts/AdditionalButtons.cs:44
260} | /Scripts/AdditionalButtons.cs:44
261 "#.trim();
262 let mut cpp_lines = Vec::new();
263 let mut cs_lines = Vec::new();
264 for (cpp_code, cs_info) in cpp_source.lines().map(|l| l.split_once('|').unwrap()) {
265 cpp_lines.push(cpp_code.trim_end());
266 cs_lines.push(
267 cs_info
268 .split_once(':')
269 .map(|(file, line)| (file.trim(), line.parse::<u32>().unwrap())),
270 );
271 }
272 let parsed_mapping = source_to_mapping(cpp_lines.join("\n").as_bytes());
273
274 for (i, expected) in cs_lines.iter().enumerate() {
275 let line_nr = i as u32 + 1;
276 println!("Checking line {: >3}: {}", line_nr, cpp_lines[i]);
277 assert_eq!(parsed_mapping.lookup("main.cpp", line_nr), *expected);
278 }
279 }
280}