1use std::collections::{BTreeMap, BTreeSet};
4
5use macho_core::model::addr::Va;
6use macho_core::model::macho_file::MachoFile;
7use macho_core::model::symbol::{Symbol, SymbolTable};
8
9use super::*;
10use crate::strict_rtti::decoder::{StrictDecoder, checked_add};
11use crate::strict_rtti::{StrictPointerTarget, StrictRttiGapCode};
12use crate::{Error, Result};
13
14#[derive(Clone, Copy)]
15struct Extent {
16 byte_length: u64,
17 source: ItaniumVtableExtentSource,
18}
19
20struct VtableDecoder<'a, 'data> {
21 reader: StrictDecoder<'a, 'data>,
22 limits: StrictVtableLimits,
23 symbols_by_va: BTreeMap<u64, Vec<String>>,
24 defined_vas: Vec<u64>,
25 total_words: u64,
26}
27
28impl<'a, 'data> VtableDecoder<'a, 'data> {
29 fn new(
30 macho: &'a MachoFile<'data>,
31 limits: StrictVtableLimits,
32 symbols: &[Symbol<'_>],
33 ) -> Result<Self> {
34 let mut symbols_by_va = BTreeMap::<u64, Vec<String>>::new();
35 let mut defined_vas = Vec::new();
36 for symbol in symbols
37 .iter()
38 .filter(|value| value.is_defined() && value.value != 0)
39 {
40 symbols_by_va
41 .entry(symbol.value)
42 .or_default()
43 .push(symbol.name.to_owned());
44 defined_vas.push(symbol.value);
45 }
46 for names in symbols_by_va.values_mut() {
47 names.sort();
48 names.dedup();
49 }
50 defined_vas.sort_unstable();
51 defined_vas.dedup();
52 let mut reader = StrictDecoder::new(macho, limits.reader_limits())?;
53 reader.symbols_by_va = symbols_by_va
54 .iter()
55 .filter_map(|(va, names)| names.first().cloned().map(|name| (*va, name)))
56 .collect();
57 Ok(Self {
58 reader,
59 limits,
60 symbols_by_va,
61 defined_vas,
62 total_words: 0,
63 })
64 }
65
66 fn extent(&self, symbol: &Symbol<'_>) -> Result<Extent> {
67 let section = self
68 .reader
69 .macho
70 .all_sections()
71 .find(|section| {
72 let start = section.addr().0;
73 start
74 .checked_add(section.size())
75 .is_some_and(|end| symbol.value >= start && symbol.value < end)
76 })
77 .ok_or_else(|| Error::address("vtable symbol is not inside a section"))?;
78 if section.section_type().is_zerofill() {
79 return Err(Error::format("vtable symbol is in a zero-fill section"));
80 }
81 let section_end = section
82 .addr()
83 .0
84 .checked_add(section.size())
85 .ok_or_else(|| Error::format("vtable section end overflows"))?;
86 let next = self
87 .defined_vas
88 .iter()
89 .copied()
90 .find(|va| *va > symbol.value && *va <= section_end);
91 let (end, source) = next
92 .map_or((section_end, ItaniumVtableExtentSource::SectionEnd), |va| {
93 (va, ItaniumVtableExtentSource::NextDefinedSymbol)
94 });
95 let byte_length = end
96 .checked_sub(symbol.value)
97 .ok_or_else(|| Error::format("vtable symbol extent underflows"))?;
98 let pointer_width = self.reader.pointer_size;
99 if byte_length == 0 || byte_length % pointer_width != 0 {
100 return Err(Error::format(
101 "vtable symbol extent is empty or not pointer aligned",
102 ));
103 }
104 Ok(Extent {
105 byte_length,
106 source,
107 })
108 }
109
110 fn admit_words(&mut self, extent: Extent) -> Result<u64> {
111 let words = extent.byte_length / self.reader.pointer_size;
112 self.total_words = self
113 .total_words
114 .checked_add(words)
115 .ok_or_else(|| Error::format("strict vtable word count overflows"))?;
116 if self.total_words > self.limits.max_words {
117 return Err(Error::format("strict vtable word limit exceeded"));
118 }
119 Ok(words)
120 }
121
122 fn decode_group(
123 &mut self,
124 symbol: &Symbol<'_>,
125 kind: ItaniumVtableSymbolKind,
126 ) -> Result<ItaniumVtableGroupRecord> {
127 let extent = self.extent(symbol)?;
128 let word_count = self.admit_words(extent)?;
129 let ptr = self.reader.pointer_size;
130 let raw = (0..word_count)
131 .map(|ordinal| {
132 let va = checked_add(
133 symbol.value,
134 ordinal
135 .checked_mul(ptr)
136 .ok_or_else(|| Error::format("vtable word address overflows"))?,
137 )?;
138 self.reader.peek_word(va)
139 })
140 .collect::<Result<Vec<_>>>()?;
141 let mut headers = Vec::new();
142 for offset_word in 0..word_count.saturating_sub(1) {
143 let typeinfo_word = offset_word + 1;
144 let va = checked_add(symbol.value, typeinfo_word * ptr)?;
145 if self.is_typeinfo_target(va)? {
146 headers.push((offset_word, ItaniumVtableAddressPointSource::TypeinfoSymbol));
147 }
148 }
149 if headers.is_empty() && word_count >= 2 && raw[1] == 0 {
150 headers.push((
151 0,
152 ItaniumVtableAddressPointSource::NullTypeinfoAtSymbolStart,
153 ));
154 }
155 if headers.is_empty() {
156 return Err(Error::format(
157 "vtable group has no structurally proven address point",
158 ));
159 }
160 let header_words = headers
161 .iter()
162 .flat_map(|(word, _)| [*word, *word + 1])
163 .collect::<BTreeSet<_>>();
164 if header_words.len() != headers.len() * 2 {
165 return Err(Error::format("vtable address-point headers overlap"));
166 }
167
168 let mut address_points = Vec::new();
169 let mut ambiguous_words = Vec::new();
170 let first_header = headers[0].0;
171 let mut first_prefix = Vec::new();
172 for word in 0..first_header {
173 first_prefix.push(self.offset_record(symbol, word, raw[word as usize])?);
174 }
175 for (index, (offset_word, source)) in headers.iter().copied().enumerate() {
176 let offset_va = checked_add(symbol.value, offset_word * ptr)?;
177 let offset_observation = self.reader.observe(
178 symbol.name,
179 format!("address_point[{index}].offset_to_top"),
180 offset_va,
181 ptr,
182 StrictRttiObservationKind::Integer,
183 )?;
184 let typeinfo = self.reader.pointer(
185 symbol.name,
186 format!("address_point[{index}].typeinfo"),
187 checked_add(offset_va, ptr)?,
188 )?;
189 let next_header = headers
190 .get(index + 1)
191 .map(|(word, _)| *word)
192 .unwrap_or(word_count);
193 let slot_start = offset_word + 2;
194 let mut slots = Vec::new();
195 let mut cursor = slot_start;
196 while cursor < next_header {
197 let va = checked_add(symbol.value, cursor * ptr)?;
198 if index + 1 < headers.len() && !self.is_exact_callable_or_null(va)? {
199 break;
200 }
201 slots.push(self.slot_record(symbol, index as u64, slots.len() as u64, cursor)?);
202 cursor += 1;
203 }
204 while cursor < next_header {
205 let va = checked_add(symbol.value, cursor * ptr)?;
206 let observation_ordinal = self.reader.observe(
207 symbol.name,
208 format!("inter_address_point[{cursor}]"),
209 va,
210 ptr,
211 StrictRttiObservationKind::Integer,
212 )?;
213 ambiguous_words.push(ItaniumVtableAmbiguousWordRecord {
214 word_ordinal: cursor,
215 observation_ordinal,
216 raw_value: raw[cursor as usize],
217 });
218 cursor += 1;
219 }
220 address_points.push(ItaniumVtableAddressPointRecord {
221 ordinal: index as u64,
222 va: checked_add(symbol.value, (offset_word + 2) * ptr)?,
223 source,
224 offset_to_top_word: offset_word,
225 prefix_offsets: if index == 0 {
226 std::mem::take(&mut first_prefix)
227 } else {
228 Vec::new()
229 },
230 offset_to_top_observation_ordinal: offset_observation,
231 offset_to_top: sign_extend(raw[offset_word as usize], ptr as u8),
232 typeinfo,
233 slots,
234 });
235 }
236 Ok(ItaniumVtableGroupRecord {
237 symbol: symbol.name.to_owned(),
238 kind,
239 va: symbol.value,
240 file_offset: self
241 .reader
242 .macho
243 .address_map()
244 .va_to_thin_offset(Va(symbol.value))?
245 .0,
246 byte_length: extent.byte_length,
247 extent_source: extent.source,
248 pointer_width: ptr as u8,
249 address_points,
250 ambiguous_words,
251 weak_definition: symbol.is_weak_def(),
252 })
253 }
254
255 fn decode_vtt(&mut self, symbol: &Symbol<'_>) -> Result<ItaniumVttRecord> {
256 let extent = self.extent(symbol)?;
257 let word_count = self.admit_words(extent)?;
258 let ptr = self.reader.pointer_size;
259 let mut entries = Vec::new();
260 for ordinal in 0..word_count {
261 let pointer = self.reader.pointer(
262 symbol.name,
263 format!("vtt[{ordinal}]"),
264 checked_add(symbol.value, ordinal * ptr)?,
265 )?;
266 if matches!(pointer.target, StrictPointerTarget::Null) {
267 return Err(Error::format("VTT entry is null"));
268 }
269 entries.push(ItaniumVttEntryRecord {
270 ordinal,
271 address_point: pointer,
272 });
273 }
274 Ok(ItaniumVttRecord {
275 symbol: symbol.name.to_owned(),
276 va: symbol.value,
277 file_offset: self
278 .reader
279 .macho
280 .address_map()
281 .va_to_thin_offset(Va(symbol.value))?
282 .0,
283 byte_length: extent.byte_length,
284 extent_source: extent.source,
285 entries,
286 weak_definition: symbol.is_weak_def(),
287 })
288 }
289
290 fn is_typeinfo_target(&self, va: u64) -> Result<bool> {
291 Ok(match self.reader.peek_pointer_target(va) {
292 Ok(StrictPointerTarget::Local { va }) => self
293 .symbols_by_va
294 .get(&va)
295 .is_some_and(|names| names.iter().any(|name| is_typeinfo_symbol(name))),
296 Ok(StrictPointerTarget::External { symbol, .. }) => is_typeinfo_symbol(&symbol),
297 Ok(StrictPointerTarget::Null) | Err(_) => false,
298 })
299 }
300
301 fn is_exact_callable_or_null(&self, va: u64) -> Result<bool> {
302 Ok(match self.reader.peek_pointer_target(va) {
303 Ok(StrictPointerTarget::Null) => true,
304 Ok(StrictPointerTarget::External { symbol, .. }) => !is_typeinfo_symbol(&symbol),
305 Ok(StrictPointerTarget::Local { va }) => self
306 .symbols_by_va
307 .get(&va)
308 .is_some_and(|names| names.iter().any(|name| !is_data_special_name(name))),
309 Err(_) => false,
310 })
311 }
312
313 fn offset_record(
314 &mut self,
315 symbol: &Symbol<'_>,
316 word_ordinal: u64,
317 raw_value: u64,
318 ) -> Result<ItaniumVtableOffsetRecord> {
319 let va = checked_add(symbol.value, word_ordinal * self.reader.pointer_size)?;
320 let observation_ordinal = self.reader.observe(
321 symbol.name,
322 format!("prefix_offset[{word_ordinal}]"),
323 va,
324 self.reader.pointer_size,
325 StrictRttiObservationKind::Integer,
326 )?;
327 Ok(ItaniumVtableOffsetRecord {
328 word_ordinal,
329 observation_ordinal,
330 raw_value,
331 signed_value: sign_extend(raw_value, self.reader.pointer_size as u8),
332 role: ItaniumVtableOffsetRole::VcallOrVbase,
333 })
334 }
335
336 fn slot_record(
337 &mut self,
338 symbol: &Symbol<'_>,
339 point: u64,
340 ordinal: u64,
341 word_ordinal: u64,
342 ) -> Result<ItaniumVtableSlotRecord> {
343 let pointer = self.reader.pointer(
344 symbol.name,
345 format!("address_point[{point}].slot[{ordinal}]"),
346 checked_add(symbol.value, word_ordinal * self.reader.pointer_size)?,
347 )?;
348 let target_symbol = exact_target_symbol(&pointer.target, &self.symbols_by_va);
349 let (role, this_adjustment, return_adjustment) =
350 classify_slot(target_symbol.as_deref(), &pointer.target)?;
351 Ok(ItaniumVtableSlotRecord {
352 ordinal,
353 word_ordinal,
354 pointer,
355 target_symbol,
356 role,
357 this_adjustment,
358 return_adjustment,
359 })
360 }
361}
362
363pub fn decode_strict_vtables(
365 macho: &MachoFile<'_>,
366 limits: StrictVtableLimits,
367) -> Result<StrictVtableBatch> {
368 limits.validate()?;
369 let input_bytes = macho.bytes().len() as u64;
370 if input_bytes > limits.max_input_bytes {
371 return preflight_batch("input_bytes", limits);
372 }
373 let symtab = macho.ext::<SymbolTable<'_>>()?;
374 if symtab.symbols().len() as u64 > limits.max_symbols {
375 return preflight_batch("symbol_registry", limits);
376 }
377 let mut candidates = symtab
378 .symbols()
379 .iter()
380 .filter_map(|symbol| classify_symbol(symbol.name).map(|kind| (symbol, kind)))
381 .collect::<Vec<_>>();
382 candidates.sort_by(|(left, _), (right, _)| {
383 left.name
384 .cmp(right.name)
385 .then_with(|| left.value.cmp(&right.value))
386 .then_with(|| left.index.cmp(&right.index))
387 });
388 let attempted = candidates.len() as u64;
389 if attempted == 0 {
390 let batch = StrictVtableBatch {
391 outcome: StrictRttiOutcome::Absent,
392 records: Vec::new(),
393 observations: Vec::new(),
394 gaps: Vec::new(),
395 conservation: StrictRttiConservation {
396 attempted: 0,
397 included: 0,
398 unknown: 0,
399 excluded: 0,
400 },
401 };
402 batch.validate(limits)?;
403 return Ok(batch);
404 }
405 if attempted > limits.max_records {
406 let batch = StrictVtableBatch {
407 outcome: StrictRttiOutcome::Rejected,
408 records: Vec::new(),
409 observations: Vec::new(),
410 gaps: vec![StrictRttiGap {
411 symbol: None,
412 field: "candidate_registry".into(),
413 code: StrictRttiGapCode::StructuralLimitExceeded,
414 source_code: None,
415 }],
416 conservation: StrictRttiConservation {
417 attempted,
418 included: 0,
419 unknown: 0,
420 excluded: attempted,
421 },
422 };
423 batch.validate(limits)?;
424 return Ok(batch);
425 }
426 let mut decoder = VtableDecoder::new(macho, limits, symtab.symbols())?;
427 let mut records = Vec::new();
428 let mut gaps = Vec::new();
429 let mut included = 0;
430 let mut unknown = 0;
431 for (symbol, kind) in candidates {
432 if symbol.is_undefined() {
433 records.push(StrictVtableRecord::External {
434 symbol_kind: kind,
435 symbol: symbol.name.to_owned(),
436 library_ordinal: i32::from(symbol.library_ordinal() as i8),
437 weak: symbol.is_weak_ref(),
438 });
439 included += 1;
440 continue;
441 }
442 let decoded = if !symbol.is_defined() || symbol.value == 0 {
443 Err(Error::format("vtable symbol is not a nonzero definition"))
444 } else {
445 match kind {
446 ItaniumVtableSymbolKind::CompleteGroup
447 | ItaniumVtableSymbolKind::ConstructionGroup => decoder
448 .decode_group(symbol, kind)
449 .map(|record| StrictVtableRecord::Group {
450 record: Box::new(record),
451 }),
452 ItaniumVtableSymbolKind::Vtt => {
453 decoder
454 .decode_vtt(symbol)
455 .map(|record| StrictVtableRecord::Vtt {
456 record: Box::new(record),
457 })
458 }
459 }
460 };
461 match decoded {
462 Ok(record) => {
463 records.push(record);
464 included += 1;
465 }
466 Err(error) => {
467 let code = if error.message().contains("limit") {
468 StrictRttiGapCode::StructuralLimitExceeded
469 } else if error.kind == crate::CppErrorKind::InvalidAddress {
470 StrictRttiGapCode::PointerUnresolved
471 } else if error.kind == crate::CppErrorKind::Unsupported {
472 StrictRttiGapCode::FamilyUnsupported
473 } else {
474 StrictRttiGapCode::RecordMalformed
475 };
476 gaps.push(StrictRttiGap {
477 symbol: Some(symbol.name.to_owned()),
478 field: match kind {
479 ItaniumVtableSymbolKind::Vtt => "vtt",
480 _ => "vtable_group",
481 }
482 .into(),
483 code,
484 source_code: Some(error.code().to_owned()),
485 });
486 unknown += 1;
487 }
488 }
489 }
490 let batch = StrictVtableBatch {
491 outcome: if gaps.is_empty() {
492 StrictRttiOutcome::Complete
493 } else {
494 StrictRttiOutcome::Rejected
495 },
496 records,
497 observations: decoder.reader.observations,
498 gaps,
499 conservation: StrictRttiConservation {
500 attempted,
501 included,
502 unknown,
503 excluded: 0,
504 },
505 };
506 batch.validate(limits)?;
507 Ok(batch)
508}
509
510fn preflight_batch(field: &str, limits: StrictVtableLimits) -> Result<StrictVtableBatch> {
511 let batch = StrictVtableBatch {
512 outcome: StrictRttiOutcome::Rejected,
513 records: Vec::new(),
514 observations: Vec::new(),
515 gaps: vec![StrictRttiGap {
516 symbol: None,
517 field: field.into(),
518 code: StrictRttiGapCode::StructuralLimitExceeded,
519 source_code: None,
520 }],
521 conservation: StrictRttiConservation {
522 attempted: 0,
523 included: 0,
524 unknown: 0,
525 excluded: 0,
526 },
527 };
528 batch.validate(limits)?;
529 Ok(batch)
530}
531
532pub fn decode_strict_vtables_from_source<S>(
534 source: &S,
535 limits: StrictVtableLimits,
536) -> Result<StrictVtableBatch>
537where
538 S: AsRef<[u8]> + ?Sized,
539{
540 let macho = crate::parse_source(source)?;
541 decode_strict_vtables(&macho, limits)
542}
543
544pub(super) fn classify_symbol(name: &str) -> Option<ItaniumVtableSymbolKind> {
545 let value = name.strip_prefix('_').unwrap_or(name);
546 if value.starts_with("_ZTV") {
547 Some(ItaniumVtableSymbolKind::CompleteGroup)
548 } else if value.starts_with("_ZTC") {
549 Some(ItaniumVtableSymbolKind::ConstructionGroup)
550 } else if value.starts_with("_ZTT") {
551 Some(ItaniumVtableSymbolKind::Vtt)
552 } else {
553 None
554 }
555}
556
557fn is_typeinfo_symbol(name: &str) -> bool {
558 let value = name.strip_prefix('_').unwrap_or(name);
559 value.starts_with("_ZTI")
560}
561
562fn is_data_special_name(name: &str) -> bool {
563 let value = name.strip_prefix('_').unwrap_or(name);
564 ["_ZTI", "_ZTS", "_ZTV", "_ZTT", "_ZTC"]
565 .iter()
566 .any(|prefix| value.starts_with(prefix))
567}
568
569fn exact_target_symbol(
570 target: &StrictPointerTarget,
571 symbols: &BTreeMap<u64, Vec<String>>,
572) -> Option<String> {
573 match target {
574 StrictPointerTarget::Local { va } => symbols.get(va).and_then(|names| {
575 names
576 .iter()
577 .find(|name| !is_data_special_name(name))
578 .or_else(|| names.first())
579 .cloned()
580 }),
581 StrictPointerTarget::External { symbol, .. } => Some(symbol.clone()),
582 StrictPointerTarget::Null => None,
583 }
584}
585
586fn classify_slot(
587 symbol: Option<&str>,
588 target: &StrictPointerTarget,
589) -> Result<(
590 ItaniumVtableSlotRole,
591 Option<ItaniumThunkAdjustment>,
592 Option<ItaniumThunkAdjustment>,
593)> {
594 if matches!(target, StrictPointerTarget::Null) {
595 return Ok((ItaniumVtableSlotRole::Null, None, None));
596 }
597 let Some(symbol) = symbol else {
598 return Ok((ItaniumVtableSlotRole::Unknown, None, None));
599 };
600 let abi = symbol.strip_prefix('_').unwrap_or(symbol);
601 if abi == "__cxa_pure_virtual" {
602 return Ok((ItaniumVtableSlotRole::PureVirtual, None, None));
603 }
604 if abi == "__cxa_deleted_virtual" {
605 return Ok((ItaniumVtableSlotRole::DeletedVirtual, None, None));
606 }
607 if abi.ends_with("D0Ev") {
608 return Ok((ItaniumVtableSlotRole::DeletingDestructor, None, None));
609 }
610 if abi.ends_with("D1Ev") {
611 return Ok((ItaniumVtableSlotRole::CompleteDestructor, None, None));
612 }
613 if abi.ends_with("D2Ev") {
614 return Ok((ItaniumVtableSlotRole::BaseDestructor, None, None));
615 }
616 if let Some(rest) = abi.strip_prefix("_ZTh") {
617 let (offset, _) = parse_number(rest)?;
618 return Ok((
619 ItaniumVtableSlotRole::NonVirtualThunk,
620 Some(ItaniumThunkAdjustment::NonVirtual { offset }),
621 None,
622 ));
623 }
624 if let Some(rest) = abi.strip_prefix("_ZTv") {
625 let (offset, rest) = parse_number(rest)?;
626 let rest = rest
627 .strip_prefix('_')
628 .ok_or_else(|| Error::format("virtual thunk lacks adjustment separator"))?;
629 let (virtual_offset, _) = parse_number(rest)?;
630 return Ok((
631 ItaniumVtableSlotRole::VirtualThunk,
632 Some(ItaniumThunkAdjustment::Virtual {
633 offset,
634 virtual_offset,
635 }),
636 None,
637 ));
638 }
639 if let Some(rest) = abi.strip_prefix("_ZTc") {
640 let (this_adjustment, rest) = parse_call_offset(rest)?;
641 let (return_adjustment, _) = parse_call_offset(rest)?;
642 return Ok((
643 ItaniumVtableSlotRole::CovariantThunk,
644 Some(this_adjustment),
645 Some(return_adjustment),
646 ));
647 }
648 Ok((ItaniumVtableSlotRole::Function, None, None))
649}
650
651fn parse_call_offset(value: &str) -> Result<(ItaniumThunkAdjustment, &str)> {
652 if let Some(rest) = value.strip_prefix('h') {
653 let (offset, rest) = parse_number(rest)?;
654 let rest = rest
655 .strip_prefix('_')
656 .ok_or_else(|| Error::format("non-virtual call offset lacks terminator"))?;
657 Ok((ItaniumThunkAdjustment::NonVirtual { offset }, rest))
658 } else if let Some(rest) = value.strip_prefix('v') {
659 let (offset, rest) = parse_number(rest)?;
660 let rest = rest
661 .strip_prefix('_')
662 .ok_or_else(|| Error::format("virtual call offset lacks first separator"))?;
663 let (virtual_offset, rest) = parse_number(rest)?;
664 let rest = rest
665 .strip_prefix('_')
666 .ok_or_else(|| Error::format("virtual call offset lacks terminator"))?;
667 Ok((
668 ItaniumThunkAdjustment::Virtual {
669 offset,
670 virtual_offset,
671 },
672 rest,
673 ))
674 } else {
675 Err(Error::format("unknown thunk call-offset discriminator"))
676 }
677}
678
679fn parse_number(value: &str) -> Result<(i64, &str)> {
680 let (negative, digits) = value
681 .strip_prefix('n')
682 .map_or((false, value), |rest| (true, rest));
683 let length = digits.bytes().take_while(u8::is_ascii_digit).count();
684 if length == 0 {
685 return Err(Error::format("thunk adjustment lacks a number"));
686 }
687 let magnitude = digits[..length]
688 .parse::<i64>()
689 .map_err(|_| Error::format("thunk adjustment overflows Int64"))?;
690 let number = if negative {
691 magnitude
692 .checked_neg()
693 .ok_or_else(|| Error::format("thunk adjustment negation overflows"))?
694 } else {
695 magnitude
696 };
697 Ok((number, &digits[length..]))
698}
699
700pub(super) fn validate_slot_semantics(slot: &ItaniumVtableSlotRecord) -> Result<()> {
701 let expected = classify_slot(slot.target_symbol.as_deref(), &slot.pointer.target)?;
702 if (
703 slot.role,
704 slot.this_adjustment.clone(),
705 slot.return_adjustment.clone(),
706 ) != expected
707 {
708 return Err(Error::format(
709 "strict vtable slot role disagrees with its target symbol",
710 ));
711 }
712 Ok(())
713}
714
715#[cfg(test)]
716mod tests {
717 use super::*;
718
719 #[test]
720 fn parses_itanium_thunk_adjustments_and_destructor_roles() {
721 let local = StrictPointerTarget::Local { va: 1 };
722 assert_eq!(
723 classify_slot(Some("__ZThn16_N1A1fEv"), &local).unwrap().1,
724 Some(ItaniumThunkAdjustment::NonVirtual { offset: -16 })
725 );
726 assert_eq!(
727 classify_slot(Some("__ZTvn8_n24_N1A1fEv"), &local)
728 .unwrap()
729 .1,
730 Some(ItaniumThunkAdjustment::Virtual {
731 offset: -8,
732 virtual_offset: -24
733 })
734 );
735 let covariant = classify_slot(Some("__ZTchn8_h16_N1A1fEv"), &local).unwrap();
736 assert_eq!(
737 covariant.1,
738 Some(ItaniumThunkAdjustment::NonVirtual { offset: -8 })
739 );
740 assert_eq!(
741 covariant.2,
742 Some(ItaniumThunkAdjustment::NonVirtual { offset: 16 })
743 );
744 assert_eq!(
745 classify_slot(Some("__ZN1AD0Ev"), &local).unwrap().0,
746 ItaniumVtableSlotRole::DeletingDestructor
747 );
748 assert_eq!(
749 classify_slot(Some("___cxa_pure_virtual"), &local)
750 .unwrap()
751 .0,
752 ItaniumVtableSlotRole::PureVirtual
753 );
754 assert_eq!(
755 classify_slot(Some("___cxa_deleted_virtual"), &local)
756 .unwrap()
757 .0,
758 ItaniumVtableSlotRole::DeletedVirtual
759 );
760 }
761}