diff --git a/ghostscope-dwarf/src/analyzer/type_context.rs b/ghostscope-dwarf/src/analyzer/type_context.rs index 77f16b19..d440b56d 100644 --- a/ghostscope-dwarf/src/analyzer/type_context.rs +++ b/ghostscope-dwarf/src/analyzer/type_context.rs @@ -2,12 +2,10 @@ use super::DwarfAnalyzer; use crate::{ indexable_element_layout, member_layout, semantics::PlanError, strip_type_aliases, CompilationUnitMetadata, CuId, MemberLayout, ModuleId, PcContext, ProjectedValueRead, - ProjectedValueStep, ResolvedType, Result, SemanticType, SourceLanguage, TypeId, TypeIdentity, - TypeInfo, TypeLayoutError, TypeOrigin, TypeProjection, TypeProjectionLayout, - ValueAdapterOutcome, ValueAdapterReport, ValueAdapterStage, ValueCapturePlan, - ValueNestedFieldPlan, ValueNestedPlan, ValueNestedVariantCondition, - ValueNestedVariantFieldPlan, ValuePresentation, ValueReadPlan, ValueReadPlanOptions, - VariableAccessSegment, VariableReadPlan, VariantSelector, + ProjectedValueStep, ResolvedType, Result, SemanticType, TypeId, TypeIdentity, TypeInfo, + TypeLayoutError, TypeOrigin, TypeProjection, TypeProjectionLayout, ValueAdapterOutcome, + ValueAdapterReport, ValueAdapterStage, ValueCapturePlan, ValueNestedFieldPlan, ValueNestedPlan, + ValueReadPlan, ValueReadPlanOptions, VariableAccessSegment, VariableReadPlan, }; use std::path::Path; @@ -340,7 +338,7 @@ impl DwarfAnalyzer { let report = self.explain_value_read_plan_with_options(current, type_module_path, options)?; match report.outcome { - ValueAdapterOutcome::NotApplicable => Ok(self.rust_aggregate_value_read_plan( + ValueAdapterOutcome::NotApplicable => Ok(self.aggregate_value_read_plan( current, type_module_path, 0, @@ -503,7 +501,7 @@ impl DwarfAnalyzer { let plan = match self.value_read_plan_shallow(current, type_module_path) { Ok(ShallowValueReadPlan::Applied(plan)) => *plan, Ok(ShallowValueReadPlan::NotApplicable) => { - return self.rust_aggregate_value_read_plan( + return self.aggregate_value_read_plan( current, type_module_path, depth, @@ -532,7 +530,7 @@ impl DwarfAnalyzer { )) } - fn rust_aggregate_value_read_plan( + fn aggregate_value_read_plan( &self, current: &ResolvedType, type_module_path: Option<&Path>, @@ -540,7 +538,8 @@ impl DwarfAnalyzer { max_nesting_depth: usize, ancestors: &mut Vec, ) -> Option { - let plan = self.rust_aggregate_value_read_plan_shallow(current, type_module_path)?; + let plan = + crate::language::build_aggregate_value_read_plan(self, current, type_module_path)?; let plan = self.enrich_nested_value_read_plan( current, plan, @@ -552,231 +551,6 @@ impl DwarfAnalyzer { plan.nested.is_some().then_some(plan) } - fn rust_aggregate_value_read_plan_shallow( - &self, - current: &ResolvedType, - type_module_path: Option<&Path>, - ) -> Option { - if current.origin.as_ref().map(|origin| origin.language) != Some(SourceLanguage::Rust) { - return None; - } - let fields = match strip_type_aliases(¤t.summary) { - TypeInfo::StructType { members, .. } => { - let mut fields = Vec::with_capacity(members.len()); - for member in members { - let projection = match self.project_resolved_type( - current, - &VariableAccessSegment::Field(member.name.clone()), - type_module_path, - ) { - Ok(projection) => projection, - Err(error) => { - tracing::debug!( - target: "ghostscope_dwarf::value_adapter", - type_name = current.summary.type_name(), - field = member.name, - %error, - "Rust aggregate could not form a nested field projection; using DWARF presentation" - ); - return None; - } - }; - let TypeProjectionLayout::Member { offset } = projection.layout else { - return None; - }; - if offset != member.offset { - return None; - } - fields.push(ProjectedValueRead { - steps: vec![ProjectedValueStep::Member { offset }], - resolved_type: projection.resolved_type, - }); - } - fields - } - TypeInfo::VariantType { .. } => Vec::new(), - _ => return None, - }; - - Some(ValueReadPlan::new( - current.clone(), - ValuePresentation::Dwarf, - ValueCapturePlan::InlineView { - output_type: strip_type_aliases(¤t.summary).clone(), - fields, - }, - )) - } - - fn rust_variant_nested_plan( - &self, - current: &ResolvedType, - type_module_path: Option<&Path>, - depth: usize, - max_nesting_depth: usize, - ancestors: &mut Vec, - ) -> Option { - let current_id = current.identity.layout_dwarf_id()?; - let TypeInfo::VariantType { variant_parts, .. } = strip_type_aliases(¤t.summary) - else { - return None; - }; - let mut fields = Vec::new(); - - for (part_index, part) in variant_parts.iter().enumerate() { - for (variant_index, variant) in part.variants.iter().enumerate() { - let Some(condition) = Self::nested_variant_condition(part, variant_index) else { - continue; - }; - for (member_index, member) in variant.members.iter().enumerate() { - let wrapper = match self.variant_member_resolved_type( - current_id, - part_index, - variant_index, - member_index, - ) { - Ok(Some(wrapper)) => wrapper, - Ok(None) => continue, - Err(error) => { - tracing::debug!( - target: "ghostscope_dwarf::value_adapter", - type_name = current.summary.type_name(), - part_index, - variant_index, - member_index, - %error, - "Rust enum payload identity could not be resolved; using DWARF presentation" - ); - continue; - } - }; - if wrapper.summary != member.member_type { - tracing::debug!( - target: "ghostscope_dwarf::value_adapter", - type_name = current.summary.type_name(), - variant = member.name, - "Rust enum payload identity did not match its parsed member type; using DWARF presentation" - ); - continue; - } - let TypeInfo::StructType { - members: payload_fields, - .. - } = strip_type_aliases(&wrapper.summary) - else { - continue; - }; - - for (payload_field_index, payload_field) in payload_fields.iter().enumerate() { - let projection = match self.project_resolved_type( - &wrapper, - &VariableAccessSegment::Field(payload_field.name.clone()), - type_module_path, - ) { - Ok(projection) => projection, - Err(error) => { - tracing::debug!( - target: "ghostscope_dwarf::value_adapter", - type_name = current.summary.type_name(), - variant = member.name, - field = payload_field.name, - %error, - "Rust enum payload field could not be projected; using DWARF presentation" - ); - continue; - } - }; - let TypeProjectionLayout::Member { offset } = projection.layout else { - continue; - }; - if offset != payload_field.offset { - continue; - } - let Some(value) = self.try_nested_value_read_plan( - &projection.resolved_type, - type_module_path, - depth + 1, - max_nesting_depth, - ancestors, - ) else { - continue; - }; - fields.push(ValueNestedVariantFieldPlan { - part_index, - variant_index, - member_index, - payload_field_index, - steps: vec![ - ProjectedValueStep::Member { - offset: member.offset, - }, - ProjectedValueStep::Member { offset }, - ], - condition: condition.clone(), - value: Box::new(value), - }); - } - } - } - } - - (!fields.is_empty()).then_some(ValueNestedPlan::Variant { fields }) - } - - fn nested_variant_condition( - part: &crate::VariantPart, - variant_index: usize, - ) -> Option { - let variant = part.variants.get(variant_index)?; - let Some(discriminant) = &part.discriminant else { - let selected = part - .variants - .iter() - .position(|variant| matches!(variant.selector, VariantSelector::Default)) - .unwrap_or(0); - return (selected == variant_index).then_some(ValueNestedVariantCondition::Always); - }; - - match &variant.selector { - VariantSelector::Ranges(ranges) if !ranges.is_empty() => { - Some(ValueNestedVariantCondition::Discriminant { - member: discriminant.clone(), - ranges: ranges.clone(), - inverted: false, - }) - } - VariantSelector::Ranges(_) => None, - VariantSelector::Default => { - let first_default = part - .variants - .iter() - .position(|variant| matches!(variant.selector, VariantSelector::Default))?; - if first_default != variant_index { - return None; - } - let ranges = part - .variants - .iter() - .filter_map(|variant| match &variant.selector { - VariantSelector::Ranges(ranges) => Some(ranges.as_slice()), - VariantSelector::Default => None, - }) - .flatten() - .cloned() - .collect::>(); - if ranges.is_empty() { - Some(ValueNestedVariantCondition::Always) - } else { - Some(ValueNestedVariantCondition::Discriminant { - member: discriminant.clone(), - ranges, - inverted: true, - }) - } - } - } - } - fn enrich_nested_value_read_plan( &self, current: &ResolvedType, @@ -797,7 +571,26 @@ impl DwarfAnalyzer { ancestors.push(type_id); } - let nested = match &plan.capture { + let language_nested = { + let mut resolve_nested = |child: &ResolvedType| { + self.try_nested_value_read_plan( + child, + type_module_path, + depth + 1, + max_nesting_depth, + ancestors, + ) + }; + crate::language::build_nested_value_read_plan( + self, + current, + &plan.capture, + type_module_path, + &mut resolve_nested, + ) + }; + + let mut build_generic_nested = || match &plan.capture { ValueCapturePlan::ProjectedValue { value } => self .try_nested_value_read_plan( &value.resolved_type, @@ -809,22 +602,6 @@ impl DwarfAnalyzer { .map(|value| ValueNestedPlan::ProjectedValue { value: Box::new(value), }), - ValueCapturePlan::InlineView { - output_type, - fields: _, - } if matches!( - strip_type_aliases(output_type), - TypeInfo::VariantType { .. } - ) => - { - self.rust_variant_nested_plan( - current, - type_module_path, - depth, - max_nesting_depth, - ancestors, - ) - } ValueCapturePlan::InlineView { fields, .. } => { let nested_fields = fields .iter() @@ -890,6 +667,10 @@ impl DwarfAnalyzer { | ValueCapturePlan::IndirectHashTable { .. } | ValueCapturePlan::IndirectBTree { .. } => None, }; + let nested = match language_nested { + crate::language::NestedValuePlanResolution::Handled(nested) => nested, + crate::language::NestedValuePlanResolution::NotApplicable => build_generic_nested(), + }; if current_id.is_some() { ancestors.pop(); @@ -1241,6 +1022,22 @@ impl crate::language::ValueAdapterContext for DwarfAnalyzer { DwarfAnalyzer::template_type_parameter(self, type_id, index) } + fn variant_member_resolved_type( + &self, + current: TypeId, + part_index: usize, + variant_index: usize, + member_index: usize, + ) -> Result> { + DwarfAnalyzer::variant_member_resolved_type( + self, + current, + part_index, + variant_index, + member_index, + ) + } + fn type_alignment(&self, type_id: TypeId) -> Result> { DwarfAnalyzer::type_alignment(self, type_id) } diff --git a/ghostscope-dwarf/src/language/adapter.rs b/ghostscope-dwarf/src/language/adapter.rs index 0afb666b..68525e32 100644 --- a/ghostscope-dwarf/src/language/adapter.rs +++ b/ghostscope-dwarf/src/language/adapter.rs @@ -228,6 +228,14 @@ pub(crate) trait ValueAdapterContext { index: usize, ) -> Result>; + fn variant_member_resolved_type( + &self, + current: TypeId, + part_index: usize, + variant_index: usize, + member_index: usize, + ) -> Result>; + fn type_alignment(&self, type_id: TypeId) -> Result>; fn tuple_member_layout( diff --git a/ghostscope-dwarf/src/language/mod.rs b/ghostscope-dwarf/src/language/mod.rs index a75e0f2d..657dceed 100644 --- a/ghostscope-dwarf/src/language/mod.rs +++ b/ghostscope-dwarf/src/language/mod.rs @@ -15,6 +15,12 @@ pub(crate) enum ValueLayout { pub(crate) type ValueLayoutResolution = adapter::ValueLayoutResolution; +#[derive(Debug, Clone, PartialEq)] +pub(crate) enum NestedValuePlanResolution { + NotApplicable, + Handled(Option), +} + /// Select an adapter only after dispatching on the type's DWARF language. /// /// This boundary prevents C, C++, and unknown-language values from entering @@ -57,6 +63,51 @@ pub(crate) fn build_value_read_plan( } } +pub(crate) fn build_aggregate_value_read_plan( + context: &dyn ValueAdapterContext, + current: &crate::ResolvedType, + type_module_path: Option<&Path>, +) -> Option { + match source_language(current) { + SourceLanguage::Rust => { + rust::build_aggregate_value_read_plan(context, current, type_module_path) + } + SourceLanguage::C + | SourceLanguage::Cpp + | SourceLanguage::Other(_) + | SourceLanguage::Unknown => None, + } +} + +pub(crate) fn build_nested_value_read_plan( + context: &dyn ValueAdapterContext, + current: &crate::ResolvedType, + capture: &crate::ValueCapturePlan, + type_module_path: Option<&Path>, + resolve_nested: &mut dyn FnMut(&crate::ResolvedType) -> Option, +) -> NestedValuePlanResolution { + let crate::ValueCapturePlan::InlineView { output_type, .. } = capture else { + return NestedValuePlanResolution::NotApplicable; + }; + if !matches!( + crate::strip_type_aliases(output_type), + crate::TypeInfo::VariantType { .. } + ) { + return NestedValuePlanResolution::NotApplicable; + } + + let plan = match source_language(current) { + SourceLanguage::Rust => { + rust::build_variant_nested_plan(context, current, type_module_path, resolve_nested) + } + SourceLanguage::C + | SourceLanguage::Cpp + | SourceLanguage::Other(_) + | SourceLanguage::Unknown => None, + }; + NestedValuePlanResolution::Handled(plan) +} + pub(crate) fn annotate_type_info(language: SourceLanguage, type_info: &mut crate::TypeInfo) { if language == SourceLanguage::Rust { rust::annotate_type_info(type_info); @@ -90,7 +141,82 @@ pub(crate) fn resolve_access_segment( #[cfg(test)] mod tests { use super::*; - use crate::{CuId, ModuleId, ResolvedType, TypeIdentity, TypeInfo}; + use crate::{ + CuId, MemberLayout, ModuleId, ProjectedValueRead, ResolvedType, TypeId, TypeIdentity, + TypeInfo, TypeProjection, + }; + + struct PanicValueAdapterContext; + + impl ValueAdapterContext for PanicValueAdapterContext { + fn project_type( + &self, + _current: &ResolvedType, + _segment: &VariableAccessSegment, + _type_module_path: Option<&Path>, + ) -> crate::Result { + panic!("non-Rust composition must not query DWARF") + } + + fn project_member_path( + &self, + _current: &ResolvedType, + _path: &[String], + _type_module_path: Option<&Path>, + ) -> crate::Result { + panic!("non-Rust composition must not query DWARF") + } + + fn project_value_path( + &self, + _current: &ResolvedType, + _path: &[ProjectedPathSegment], + _type_module_path: Option<&Path>, + _capture_address: bool, + ) -> crate::Result> { + panic!("non-Rust composition must not query DWARF") + } + + fn template_type_parameter( + &self, + _type_id: TypeId, + _index: usize, + ) -> crate::Result> { + panic!("non-Rust composition must not query DWARF") + } + + fn variant_member_resolved_type( + &self, + _current: TypeId, + _part_index: usize, + _variant_index: usize, + _member_index: usize, + ) -> crate::Result> { + panic!("non-Rust composition must not query DWARF") + } + + fn type_alignment(&self, _type_id: TypeId) -> crate::Result> { + panic!("non-Rust composition must not query DWARF") + } + + fn tuple_member_layout( + &self, + _type_id: TypeId, + _aggregate_type: &TypeInfo, + _index: u32, + ) -> crate::Result { + panic!("non-Rust composition must not query DWARF") + } + + fn resolve_aggregate_type_in_module( + &self, + _anchor: TypeId, + _lookup_names: &[&str], + _exact_qualified_name: Option<&str>, + ) -> crate::Result> { + panic!("non-Rust composition must not query DWARF") + } + } fn origin(language: SourceLanguage) -> TypeOrigin { TypeOrigin { @@ -144,4 +270,99 @@ mod tests { ); assert!(!requires_dwarf_qualified_name(¤t)); } + + #[test] + fn non_rust_aggregates_bypass_rust_composition() { + let current = ResolvedType::new( + TypeInfo::StructType { + name: "Request".to_string(), + size: 0, + members: Vec::new(), + }, + TypeIdentity::Unknown, + Some(origin(SourceLanguage::C)), + ); + let context = PanicValueAdapterContext; + + assert_eq!( + build_aggregate_value_read_plan(&context, ¤t, None), + None + ); + let mut resolve_nested = + |_child: &ResolvedType| panic!("non-Rust composition must not resolve nested values"); + assert_eq!( + build_nested_value_read_plan( + &context, + ¤t, + &crate::ValueCapturePlan::InlineView { + output_type: current.summary.clone(), + fields: Vec::new(), + }, + None, + &mut resolve_nested, + ), + NestedValuePlanResolution::NotApplicable + ); + } + + #[test] + fn ordinary_rust_inline_views_bypass_language_nested_composition() { + let current = ResolvedType::new( + TypeInfo::StructType { + name: "Request".to_string(), + size: 0, + members: Vec::new(), + }, + TypeIdentity::Unknown, + Some(origin(SourceLanguage::Rust)), + ); + let context = PanicValueAdapterContext; + let mut resolve_nested = + |_child: &ResolvedType| panic!("ordinary inline views use generic recursion"); + + assert_eq!( + build_nested_value_read_plan( + &context, + ¤t, + &crate::ValueCapturePlan::InlineView { + output_type: current.summary.clone(), + fields: Vec::new(), + }, + None, + &mut resolve_nested, + ), + NestedValuePlanResolution::NotApplicable + ); + } + + #[test] + fn non_rust_variants_are_handled_without_rust_composition() { + let current = ResolvedType::new( + TypeInfo::VariantType { + name: "Choice".to_string(), + size: 0, + members: Vec::new(), + variant_parts: Vec::new(), + }, + TypeIdentity::Unknown, + Some(origin(SourceLanguage::C)), + ); + let context = PanicValueAdapterContext; + let mut resolve_nested = + |_child: &ResolvedType| panic!("non-Rust variants must not resolve nested values"); + + assert_eq!( + build_nested_value_read_plan( + &context, + ¤t, + &crate::ValueCapturePlan::InlineView { + output_type: current.summary.clone(), + fields: Vec::new(), + }, + None, + &mut resolve_nested, + ), + NestedValuePlanResolution::Handled(None) + ); + } } diff --git a/ghostscope-dwarf/src/language/rust/composition.rs b/ghostscope-dwarf/src/language/rust/composition.rs new file mode 100644 index 00000000..44577625 --- /dev/null +++ b/ghostscope-dwarf/src/language/rust/composition.rs @@ -0,0 +1,308 @@ +use std::path::Path; + +use crate::language::adapter::ValueAdapterContext; +use crate::{ + strip_type_aliases, ProjectedValueRead, ProjectedValueStep, ResolvedType, SourceLanguage, + TypeInfo, TypeProjectionLayout, ValueCapturePlan, ValueNestedPlan, ValueNestedVariantCondition, + ValueNestedVariantFieldPlan, ValuePresentation, ValueReadPlan, VariableAccessSegment, + VariantSelector, +}; + +pub(super) fn build_aggregate_value_read_plan( + context: &dyn ValueAdapterContext, + current: &ResolvedType, + type_module_path: Option<&Path>, +) -> Option { + if current.origin.as_ref().map(|origin| origin.language) != Some(SourceLanguage::Rust) { + return None; + } + let fields = match strip_type_aliases(¤t.summary) { + TypeInfo::StructType { members, .. } => { + let mut fields = Vec::with_capacity(members.len()); + for member in members { + let projection = match context.project_type( + current, + &VariableAccessSegment::Field(member.name.clone()), + type_module_path, + ) { + Ok(projection) => projection, + Err(error) => { + tracing::debug!( + target: "ghostscope_dwarf::value_adapter", + type_name = current.summary.type_name(), + field = member.name, + %error, + "Rust aggregate could not form a nested field projection; using DWARF presentation" + ); + return None; + } + }; + let TypeProjectionLayout::Member { offset } = projection.layout else { + return None; + }; + if offset != member.offset { + return None; + } + fields.push(ProjectedValueRead { + steps: vec![ProjectedValueStep::Member { offset }], + resolved_type: projection.resolved_type, + }); + } + fields + } + TypeInfo::VariantType { .. } => Vec::new(), + _ => return None, + }; + + Some(ValueReadPlan::new( + current.clone(), + ValuePresentation::Dwarf, + ValueCapturePlan::InlineView { + output_type: strip_type_aliases(¤t.summary).clone(), + fields, + }, + )) +} + +pub(super) fn build_variant_nested_plan( + context: &dyn ValueAdapterContext, + current: &ResolvedType, + type_module_path: Option<&Path>, + resolve_nested: &mut dyn FnMut(&ResolvedType) -> Option, +) -> Option { + if current.origin.as_ref().map(|origin| origin.language) != Some(SourceLanguage::Rust) { + return None; + } + let current_id = current.identity.layout_dwarf_id()?; + let TypeInfo::VariantType { variant_parts, .. } = strip_type_aliases(¤t.summary) else { + return None; + }; + let mut fields = Vec::new(); + + for (part_index, part) in variant_parts.iter().enumerate() { + for (variant_index, variant) in part.variants.iter().enumerate() { + let Some(condition) = nested_variant_condition(part, variant_index) else { + continue; + }; + for (member_index, member) in variant.members.iter().enumerate() { + let wrapper = match context.variant_member_resolved_type( + current_id, + part_index, + variant_index, + member_index, + ) { + Ok(Some(wrapper)) => wrapper, + Ok(None) => continue, + Err(error) => { + tracing::debug!( + target: "ghostscope_dwarf::value_adapter", + type_name = current.summary.type_name(), + part_index, + variant_index, + member_index, + %error, + "Rust enum payload identity could not be resolved; using DWARF presentation" + ); + continue; + } + }; + if wrapper.summary != member.member_type { + tracing::debug!( + target: "ghostscope_dwarf::value_adapter", + type_name = current.summary.type_name(), + variant = member.name, + "Rust enum payload identity did not match its parsed member type; using DWARF presentation" + ); + continue; + } + let TypeInfo::StructType { + members: payload_fields, + .. + } = strip_type_aliases(&wrapper.summary) + else { + continue; + }; + + for (payload_field_index, payload_field) in payload_fields.iter().enumerate() { + let projection = match context.project_type( + &wrapper, + &VariableAccessSegment::Field(payload_field.name.clone()), + type_module_path, + ) { + Ok(projection) => projection, + Err(error) => { + tracing::debug!( + target: "ghostscope_dwarf::value_adapter", + type_name = current.summary.type_name(), + variant = member.name, + field = payload_field.name, + %error, + "Rust enum payload field could not be projected; using DWARF presentation" + ); + continue; + } + }; + let TypeProjectionLayout::Member { offset } = projection.layout else { + continue; + }; + if offset != payload_field.offset { + continue; + } + let Some(value) = resolve_nested(&projection.resolved_type) else { + continue; + }; + fields.push(ValueNestedVariantFieldPlan { + part_index, + variant_index, + member_index, + payload_field_index, + steps: vec![ + ProjectedValueStep::Member { + offset: member.offset, + }, + ProjectedValueStep::Member { offset }, + ], + condition: condition.clone(), + value: Box::new(value), + }); + } + } + } + } + + (!fields.is_empty()).then_some(ValueNestedPlan::Variant { fields }) +} + +fn nested_variant_condition( + part: &crate::VariantPart, + variant_index: usize, +) -> Option { + let variant = part.variants.get(variant_index)?; + let Some(discriminant) = &part.discriminant else { + let selected = part + .variants + .iter() + .position(|variant| matches!(variant.selector, VariantSelector::Default)) + .unwrap_or(0); + return (selected == variant_index).then_some(ValueNestedVariantCondition::Always); + }; + + match &variant.selector { + VariantSelector::Ranges(ranges) if !ranges.is_empty() => { + Some(ValueNestedVariantCondition::Discriminant { + member: discriminant.clone(), + ranges: ranges.clone(), + inverted: false, + }) + } + VariantSelector::Ranges(_) => None, + VariantSelector::Default => { + let first_default = part + .variants + .iter() + .position(|variant| matches!(variant.selector, VariantSelector::Default))?; + if first_default != variant_index { + return None; + } + let ranges = part + .variants + .iter() + .filter_map(|variant| match &variant.selector { + VariantSelector::Ranges(ranges) => Some(ranges.as_slice()), + VariantSelector::Default => None, + }) + .flatten() + .cloned() + .collect::>(); + if ranges.is_empty() { + Some(ValueNestedVariantCondition::Always) + } else { + Some(ValueNestedVariantCondition::Discriminant { + member: discriminant.clone(), + ranges, + inverted: true, + }) + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ + DiscriminantRange, DiscriminantValue, StructMember, VariantCase, VariantPart, + VariantPayloadPresentation, + }; + + fn member(name: &str) -> StructMember { + StructMember { + name: name.to_string(), + member_type: TypeInfo::UnknownType { + name: "discriminant".to_string(), + }, + offset: 0, + bit_offset: None, + bit_size: None, + } + } + + fn variant(selector: VariantSelector) -> VariantCase { + VariantCase { + selector, + members: Vec::new(), + variant_parts: Vec::new(), + payload_presentation: VariantPayloadPresentation::Dwarf, + } + } + + #[test] + fn variant_conditions_preserve_explicit_and_default_selection() { + let discriminant = member("tag"); + let ranges = vec![DiscriminantRange { + start: DiscriminantValue::Unsigned(1), + end: DiscriminantValue::Unsigned(2), + }]; + let part = VariantPart { + discriminant: Some(discriminant.clone()), + variants: vec![ + variant(VariantSelector::Ranges(ranges.clone())), + variant(VariantSelector::Default), + ], + }; + + assert_eq!( + nested_variant_condition(&part, 0), + Some(ValueNestedVariantCondition::Discriminant { + member: discriminant.clone(), + ranges: ranges.clone(), + inverted: false, + }) + ); + assert_eq!( + nested_variant_condition(&part, 1), + Some(ValueNestedVariantCondition::Discriminant { + member: discriminant, + ranges, + inverted: true, + }) + ); + } + + #[test] + fn univariant_conditions_select_only_the_first_default() { + let part = VariantPart { + discriminant: None, + variants: vec![ + variant(VariantSelector::Default), + variant(VariantSelector::Default), + ], + }; + + assert_eq!( + nested_variant_condition(&part, 0), + Some(ValueNestedVariantCondition::Always) + ); + assert_eq!(nested_variant_condition(&part, 1), None); + } +} diff --git a/ghostscope-dwarf/src/language/rust/mod.rs b/ghostscope-dwarf/src/language/rust/mod.rs index a6c1ebf5..25c57278 100644 --- a/ghostscope-dwarf/src/language/rust/mod.rs +++ b/ghostscope-dwarf/src/language/rust/mod.rs @@ -1,4 +1,5 @@ mod access; +mod composition; mod plan; mod value; mod variant; @@ -32,3 +33,20 @@ pub(super) fn build_value_read_plan( ) -> crate::Result> { plan::build_value_read_plan(context, current, layout, type_module_path) } + +pub(super) fn build_aggregate_value_read_plan( + context: &dyn crate::language::adapter::ValueAdapterContext, + current: &crate::ResolvedType, + type_module_path: Option<&std::path::Path>, +) -> Option { + composition::build_aggregate_value_read_plan(context, current, type_module_path) +} + +pub(super) fn build_variant_nested_plan( + context: &dyn crate::language::adapter::ValueAdapterContext, + current: &crate::ResolvedType, + type_module_path: Option<&std::path::Path>, + resolve_nested: &mut dyn FnMut(&crate::ResolvedType) -> Option, +) -> Option { + composition::build_variant_nested_plan(context, current, type_module_path, resolve_nested) +}