From 2deacce6a098cb05c1627826de5aae114070b8d9 Mon Sep 17 00:00:00 2001 From: swananan Date: Tue, 14 Jul 2026 23:52:05 +0800 Subject: [PATCH] refactor: establish language-aware type projection Separate source-language dispatch from generic layout and C-style cast syntax. Carry DWARF identity and origin through atomic type projections so dynamic member, tuple, index, and cast paths remain consistent. --- e2e-tests/tests/rust_script_execution.rs | 48 +++ ghostscope-compiler/src/ebpf/codegen/args.rs | 6 +- ghostscope-compiler/src/ebpf/dwarf_bridge.rs | 2 +- .../src/ebpf/expression/casts.rs | 130 ++++--- .../src/ebpf/expression/dwarf_access.rs | 348 ++++++++--------- .../src/ebpf/expression/mod.rs | 11 +- .../src/ebpf/expression/runtime_address.rs | 4 +- ghostscope-dwarf/src/analyzer/type_context.rs | 165 +++++++- ghostscope-dwarf/src/analyzer/type_lookup.rs | 367 +++++++++++------- .../src/language/{rust.rs => rust/access.rs} | 0 ghostscope-dwarf/src/language/rust/mod.rs | 5 + ghostscope-dwarf/src/lib.rs | 30 +- ghostscope-dwarf/src/objfile/variables.rs | 34 +- .../semantics/{c_types.rs => c_integer.rs} | 206 +--------- ghostscope-dwarf/src/semantics/mod.rs | 18 +- .../src/semantics/type_context.rs | 279 ++++++++++++- ghostscope-dwarf/src/semantics/type_layout.rs | 181 +++++++++ ghostscope-dwarf/src/semantics/types.rs | 15 + .../src/semantics/variable_plan/mod.rs | 2 + .../src/semantics/variable_plan/tests.rs | 23 +- ghostscope-dwarf/src/type_syntax/c_style.rs | 94 +++++ ghostscope-dwarf/src/type_syntax/mod.rs | 3 + 22 files changed, 1344 insertions(+), 627 deletions(-) rename ghostscope-dwarf/src/language/{rust.rs => rust/access.rs} (100%) create mode 100644 ghostscope-dwarf/src/language/rust/mod.rs rename ghostscope-dwarf/src/semantics/{c_types.rs => c_integer.rs} (50%) create mode 100644 ghostscope-dwarf/src/semantics/type_layout.rs create mode 100644 ghostscope-dwarf/src/type_syntax/c_style.rs create mode 100644 ghostscope-dwarf/src/type_syntax/mod.rs diff --git a/e2e-tests/tests/rust_script_execution.rs b/e2e-tests/tests/rust_script_execution.rs index 27ba2088..58f99d56 100644 --- a/e2e-tests/tests/rust_script_execution.rs +++ b/e2e-tests/tests/rust_script_execution.rs @@ -71,6 +71,54 @@ async fn test_rust_tuple_projection_plan_preserves_semantic_identity() -> anyhow assert_eq!(layout.offset, 4); assert_eq!(layout.member_type.type_name(), "i32"); + let cast_type = analyzer + .try_resolve_c_style_semantic_type_spec_in_module(&binary_path, "Pair *")? + .ok_or_else(|| anyhow::anyhow!("expected semantic Pair pointer type"))?; + assert_eq!( + cast_type.origin.as_ref().map(|origin| origin.language), + Some(ghostscope_dwarf::SourceLanguage::Rust) + ); + let pointee = analyzer.project_resolved_type( + &cast_type, + &ghostscope_dwarf::VariableAccessSegment::Dereference, + Some(&binary_path), + )?; + assert_eq!( + pointee.layout, + ghostscope_dwarf::TypeProjectionLayout::Dereference + ); + assert_eq!( + pointee.resolved_type.identity.layout_dwarf_id(), + pair_plan.type_id + ); + assert!(matches!( + ghostscope_dwarf::strip_type_aliases(&pointee.resolved_type.summary), + ghostscope_dwarf::TypeInfo::StructType { name, .. } if name == "Pair" + )); + + let pair_member = analyzer.project_resolved_type( + &pointee.resolved_type, + &ghostscope_dwarf::VariableAccessSegment::TupleIndex(0), + Some(&binary_path), + )?; + assert_eq!( + pair_member.layout, + ghostscope_dwarf::TypeProjectionLayout::Member { offset: 0 } + ); + assert_eq!(pair_member.resolved_type.summary.type_name(), "i32"); + assert!(pair_member + .resolved_type + .identity + .layout_dwarf_id() + .is_some()); + assert_eq!( + pair_member + .resolved_type + .origin + .map(|origin| origin.language), + Some(ghostscope_dwarf::SourceLanguage::Rust) + ); + Ok(()) } diff --git a/ghostscope-compiler/src/ebpf/codegen/args.rs b/ghostscope-compiler/src/ebpf/codegen/args.rs index be03acdb..80f922fc 100644 --- a/ghostscope-compiler/src/ebpf/codegen/args.rs +++ b/ghostscope-compiler/src/ebpf/codegen/args.rs @@ -101,7 +101,7 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { expr: &crate::script::ast::Expr, lvalue: crate::ebpf::expression::DynamicLvalue<'ctx>, ) -> Result> { - let dwarf_type = lvalue.type_info.dwarf_type; + let dwarf_type = lvalue.type_info.resolved_type.summary; let data_len = Self::compute_read_size_for_type(&dwarf_type); if data_len == 0 { return Err(CodeGenError::TypeSizeNotAvailable(self.expr_to_name(expr))); @@ -351,7 +351,7 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { E::AddressOf(inner) => { if let Some(lvalue) = self.dynamic_lvalue_address_and_type(inner)? { let ptr_ty = ghostscope_dwarf::TypeInfo::PointerType { - target_type: Box::new(lvalue.type_info.dwarf_type), + target_type: Box::new(lvalue.type_info.resolved_type.summary), size: 8, }; return Ok(ComplexArg { @@ -515,7 +515,7 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { if var .dwarf_type .as_ref() - .is_some_and(ghostscope_dwarf::is_c_pointer_or_array_type) + .is_some_and(ghostscope_dwarf::is_pointer_or_array_type) { let pointed_plan = self.plan_dwarf_pointer_element_index(&var, index)?; diff --git a/ghostscope-compiler/src/ebpf/dwarf_bridge.rs b/ghostscope-compiler/src/ebpf/dwarf_bridge.rs index 7368599f..51d7a3b6 100644 --- a/ghostscope-compiler/src/ebpf/dwarf_bridge.rs +++ b/ghostscope-compiler/src/ebpf/dwarf_bridge.rs @@ -483,7 +483,7 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { }; let addr = self.resolve_planned_address(address, runtime_status_ptr, module_hint)?; - if ghostscope_dwarf::is_c_aggregate_type(dwarf_type) { + if ghostscope_dwarf::is_aggregate_type(dwarf_type) { let ptr_ty = self.context.ptr_type(inkwell::AddressSpace::default()); let as_ptr = self .builder diff --git a/ghostscope-compiler/src/ebpf/expression/casts.rs b/ghostscope-compiler/src/ebpf/expression/casts.rs index 0d81b82d..8c0a7475 100644 --- a/ghostscope-compiler/src/ebpf/expression/casts.rs +++ b/ghostscope-compiler/src/ebpf/expression/casts.rs @@ -1,7 +1,7 @@ use super::{DynamicLvalue, DynamicTypeInfo, IndexableElementInfo}; use crate::ebpf::context::{CodeGenError, EbpfContext, Result, RuntimeAddress}; use crate::script::Expr; -use ghostscope_dwarf::TypeInfo as DwarfType; +use ghostscope_dwarf::{TypeInfo as DwarfType, TypeProjectionLayout, VariableAccessSegment}; use inkwell::values::{BasicValueEnum, IntValue}; use inkwell::AddressSpace; use std::path::{Path, PathBuf}; @@ -12,12 +12,12 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { return self .resolve_cast_target_type(target_type) .ok() - .is_some_and(|ty| ghostscope_dwarf::is_c_aggregate_type(&ty)); + .is_some_and(|ty| ghostscope_dwarf::is_aggregate_type(&ty)); } if let Ok(Some(var)) = self.query_dwarf_for_complex_expr(expr) { if let Some(ref ty) = var.dwarf_type { - return ghostscope_dwarf::is_c_aggregate_type(ty); + return ghostscope_dwarf::is_aggregate_type(ty); } } false @@ -58,7 +58,7 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { if let Ok(Some(var)) = self.query_dwarf_for_complex_expr(expr) { if let Some(ref ty) = var.dwarf_type { - if ghostscope_dwarf::is_c_pointer_or_array_type(ty) { + if ghostscope_dwarf::is_pointer_or_array_type(ty) { return true; } } @@ -67,22 +67,41 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { } pub(in crate::ebpf) fn resolve_cast_target_type(&self, target_type: &str) -> Result { + self.resolve_cast_target(target_type) + .map(|resolved| resolved.summary) + } + + pub(in crate::ebpf) fn resolve_cast_target( + &self, + target_type: &str, + ) -> Result { let analyzer = self.process_analyzer; let resolved = if let Some(context) = self.current_compile_time_context.as_ref() { let module_path = Path::new(&context.module_path); analyzer - .map(|analyzer| analyzer.try_resolve_type_spec_in_module(module_path, target_type)) + .map(|analyzer| { + analyzer + .try_resolve_c_style_semantic_type_spec_in_module(module_path, target_type) + }) .transpose() .map_err(|err| CodeGenError::DwarfError(err.to_string()))? .flatten() - .or_else(|| ghostscope_dwarf::DwarfAnalyzer::resolve_builtin_type_spec(target_type)) + .or_else(|| { + ghostscope_dwarf::DwarfAnalyzer::resolve_builtin_c_style_semantic_type_spec( + target_type, + ) + }) } else { analyzer - .map(|analyzer| analyzer.try_resolve_type_spec(target_type)) + .map(|analyzer| analyzer.try_resolve_c_style_semantic_type_spec(target_type)) .transpose() .map_err(|err| CodeGenError::DwarfError(err.to_string()))? .flatten() - .or_else(|| ghostscope_dwarf::DwarfAnalyzer::resolve_builtin_type_spec(target_type)) + .or_else(|| { + ghostscope_dwarf::DwarfAnalyzer::resolve_builtin_c_style_semantic_type_spec( + target_type, + ) + }) }; resolved.ok_or_else(|| { @@ -90,6 +109,26 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { }) } + pub(super) fn dynamic_type_info( + &self, + resolved_type: ghostscope_dwarf::ResolvedType, + fallback_module_path: Option, + ) -> DynamicTypeInfo { + let type_module_path = resolved_type + .origin + .as_ref() + .and_then(|origin| { + self.process_analyzer + .and_then(|analyzer| analyzer.module_path_for_id(origin.module)) + }) + .map(Path::to_path_buf) + .or(fallback_module_path); + DynamicTypeInfo { + resolved_type, + type_module_path, + } + } + pub(super) fn cast_pointer_target_type(target_type: &DwarfType) -> Option { match ghostscope_dwarf::strip_type_aliases(target_type) { DwarfType::PointerType { target_type, .. } => Some(target_type.as_ref().clone()), @@ -197,32 +236,32 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { expr: &Expr, target_type: &str, ) -> Result> { - let target_type = self.resolve_cast_target_type(target_type)?; + let target = self.resolve_cast_target(target_type)?; let module_path = self .current_compile_time_context .as_ref() .map(|context| PathBuf::from(&context.module_path)); - if let Some(pointee_type) = Self::cast_pointer_target_type(&target_type) { + let target_type_info = self.dynamic_type_info(target, module_path); + + if matches!( + ghostscope_dwarf::strip_type_aliases(&target_type_info.resolved_type.summary), + DwarfType::PointerType { .. } + ) { + let projection = + self.project_dynamic_type(&target_type_info, &VariableAccessSegment::Dereference)?; let address = self.cast_source_pointer_value(expr)?; return Ok(DynamicLvalue { address, - type_info: DynamicTypeInfo { - dwarf_type: pointee_type, - module_path, - type_id: None, - }, + type_info: self + .dynamic_type_info(projection.resolved_type, target_type_info.type_module_path), }); } let address = self.cast_source_memory_address(expr)?; Ok(DynamicLvalue { address, - type_info: DynamicTypeInfo { - dwarf_type: target_type, - module_path, - type_id: None, - }, + type_info: target_type_info, }) } @@ -238,26 +277,23 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { return Ok(None); }; - let target_type = self.resolve_cast_target_type(target_type)?; + let target = self.resolve_cast_target(target_type)?; let module_path = self .current_compile_time_context .as_ref() .map(|context| PathBuf::from(&context.module_path)); - match ghostscope_dwarf::strip_type_aliases(&target_type) { + let target_type_info = self.dynamic_type_info(target, module_path); + match ghostscope_dwarf::strip_type_aliases(&target_type_info.resolved_type.summary) { DwarfType::PointerType { .. } => { - let Some(element_info) = - Self::indexable_info_from_type(&target_type, module_path, None) - else { + let Some(element_info) = self.project_indexable_type(&target_type_info)? else { return Ok(None); }; let base_address = self.cast_source_pointer_value(source_expr)?; Ok(Some((element_info, base_address))) } DwarfType::ArrayType { .. } => { - let Some(element_info) = - Self::indexable_info_from_type(&target_type, module_path, None) - else { + let Some(element_info) = self.project_indexable_type(&target_type_info)? else { return Ok(None); }; let base_address = self.cast_source_memory_address(source_expr)?; @@ -267,17 +303,25 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { } } - pub(super) fn indexable_info_from_type( - dwarf_type: &DwarfType, - module_path: Option, - type_id: Option, - ) -> Option { - ghostscope_dwarf::indexable_element_layout(dwarf_type).map(|layout| IndexableElementInfo { - element_type: layout.element_type, - stride: layout.stride, - module_path, - type_id, - }) + pub(super) fn project_indexable_type( + &self, + type_info: &DynamicTypeInfo, + ) -> Result> { + if !ghostscope_dwarf::is_pointer_or_array_type(&type_info.resolved_type.summary) { + return Ok(None); + } + let projection = + self.project_dynamic_type(type_info, &VariableAccessSegment::ArrayIndex(0))?; + let TypeProjectionLayout::Element { stride } = projection.layout else { + return Err(CodeGenError::DwarfError( + "index projection did not produce an element layout".to_string(), + )); + }; + Ok(Some(IndexableElementInfo { + type_info: self + .dynamic_type_info(projection.resolved_type, type_info.type_module_path.clone()), + stride, + })) } pub(super) fn compiled_pointer_value_to_runtime_address( @@ -327,11 +371,7 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { Ok(DynamicLvalue { address: base_address.with_value(element_address), - type_info: DynamicTypeInfo { - dwarf_type: element_info.element_type, - module_path: element_info.module_path, - type_id: element_info.type_id, - }, + type_info: element_info.type_info, }) } @@ -447,7 +487,7 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { .map_err(|err| CodeGenError::Builder(err.to_string())); } - if ghostscope_dwarf::is_c_aggregate_type(&target_type) { + if ghostscope_dwarf::is_aggregate_type(&target_type) { let address = self.cast_source_memory_address(expr)?; let ptr_ty = self.context.ptr_type(AddressSpace::default()); return self diff --git a/ghostscope-compiler/src/ebpf/expression/dwarf_access.rs b/ghostscope-compiler/src/ebpf/expression/dwarf_access.rs index fa098997..9bf30e9f 100644 --- a/ghostscope-compiler/src/ebpf/expression/dwarf_access.rs +++ b/ghostscope-compiler/src/ebpf/expression/dwarf_access.rs @@ -2,8 +2,9 @@ use super::{DynamicLvalue, DynamicTypeInfo, IndexableElementInfo}; use crate::ebpf::context::{CodeGenError, EbpfContext, Result, RuntimeAddress}; use crate::script::Expr; use ghostscope_dwarf::{ - AmbiguityReason, Availability, RuntimeRequirement, TypeInfo as DwarfType, TypeLayoutError, - UnsupportedReason, VariableAccessSegment, VariableReadPlan, + AmbiguityReason, Availability, ResolvedType, RuntimeRequirement, TypeIdentity, + TypeInfo as DwarfType, TypeProjection, TypeProjectionLayout, UnsupportedReason, + VariableAccessSegment, VariableReadPlan, }; use inkwell::values::{BasicValueEnum, IntValue, PointerValue}; use inkwell::AddressSpace; @@ -95,8 +96,10 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { } if matches!(expr, crate::script::Expr::PointerDeref(_)) { if let Some(lvalue) = self.dynamic_lvalue_address_and_type(expr)? { - return self - .read_dynamic_address_value(lvalue.address, &lvalue.type_info.dwarf_type); + return self.read_dynamic_address_value( + lvalue.address, + &lvalue.type_info.resolved_type.summary, + ); } } @@ -137,9 +140,12 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { let value = self.read_dynamic_address_value( element_lvalue.address, - &element_lvalue.type_info.dwarf_type, + &element_lvalue.type_info.resolved_type.summary, )?; - Ok(Some((value, element_lvalue.type_info.dwarf_type))) + Ok(Some(( + value, + element_lvalue.type_info.resolved_type.summary, + ))) } pub(in crate::ebpf) fn compile_dynamic_member_access_value( @@ -154,8 +160,22 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { let Some(element_lvalue) = self.dynamic_member_base_address_and_type(object_lvalue)? else { return Ok(None); }; - let (member_offset, member_type) = - self.dynamic_member_offset_and_type(&element_lvalue.type_info, field)?; + let projection = self.project_dynamic_type( + &element_lvalue.type_info, + &VariableAccessSegment::Field(field.to_string()), + )?; + let TypeProjectionLayout::Member { + offset: member_offset, + } = projection.layout + else { + return Err(CodeGenError::DwarfError( + "member projection did not produce a member layout".to_string(), + )); + }; + let member_type_info = self.dynamic_type_info( + projection.resolved_type, + element_lvalue.type_info.type_module_path, + ); let member_offset = self.context.i64_type().const_int(member_offset, false); let member_address = self @@ -168,9 +188,9 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { .map_err(|err| CodeGenError::Builder(err.to_string()))?; let value = self.read_dynamic_address_value( element_lvalue.address.with_value(member_address), - &member_type, + &member_type_info.resolved_type.summary, )?; - Ok(Some((value, member_type))) + Ok(Some((value, member_type_info.resolved_type.summary))) } pub(in crate::ebpf) fn compile_dynamic_tuple_access_value( @@ -182,7 +202,7 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { let Some(member_lvalue) = self.dynamic_lvalue_address_and_type(&tuple_expr)? else { return Ok(None); }; - let member_type = member_lvalue.type_info.dwarf_type; + let member_type = member_lvalue.type_info.resolved_type.summary; let value = self.read_dynamic_address_value(member_lvalue.address, &member_type)?; Ok(Some((value, member_type))) } @@ -235,11 +255,7 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { let element_address = self.resolve_runtime_address_from_expr(expr)?; return Ok(Some(DynamicLvalue { address: element_address, - type_info: DynamicTypeInfo { - dwarf_type: element_info.element_type, - module_path: element_info.module_path, - type_id: element_info.type_id, - }, + type_info: element_info.type_info, })); } @@ -251,12 +267,18 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { else { return Ok(None); }; - let (member_offset, member_type) = - self.dynamic_member_offset_and_type(&base_lvalue.type_info, field)?; - let member_type_id = self.project_dynamic_type_id( - base_lvalue.type_info.type_id, + let projection = self.project_dynamic_type( + &base_lvalue.type_info, &VariableAccessSegment::Field(field.clone()), )?; + let TypeProjectionLayout::Member { + offset: member_offset, + } = projection.layout + else { + return Err(CodeGenError::DwarfError( + "member projection did not produce a member layout".to_string(), + )); + }; let member_offset = self.context.i64_type().const_int(member_offset, false); let member_address = self .builder @@ -268,11 +290,10 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { .map_err(|err| CodeGenError::Builder(err.to_string()))?; return Ok(Some(DynamicLvalue { address: base_lvalue.address.with_value(member_address), - type_info: DynamicTypeInfo { - dwarf_type: member_type, - module_path: base_lvalue.type_info.module_path, - type_id: member_type_id, - }, + type_info: self.dynamic_type_info( + projection.resolved_type, + base_lvalue.type_info.type_module_path, + ), })); } @@ -284,13 +305,18 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { else { return Ok(None); }; - let module_path = base_lvalue.type_info.module_path.clone(); - let (member_offset, member_type) = - self.dynamic_tuple_offset_and_type(&base_lvalue.type_info, *index)?; - let member_type_id = self.project_dynamic_type_id( - base_lvalue.type_info.type_id, + let projection = self.project_dynamic_type( + &base_lvalue.type_info, &VariableAccessSegment::TupleIndex(*index), )?; + let TypeProjectionLayout::Member { + offset: member_offset, + } = projection.layout + else { + return Err(CodeGenError::DwarfError( + "tuple projection did not produce a member layout".to_string(), + )); + }; let member_offset = self.context.i64_type().const_int(member_offset, false); let member_address = self .builder @@ -302,11 +328,10 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { .map_err(|err| CodeGenError::Builder(err.to_string()))?; return Ok(Some(DynamicLvalue { address: base_lvalue.address.with_value(member_address), - type_info: DynamicTypeInfo { - dwarf_type: member_type, - module_path, - type_id: member_type_id, - }, + type_info: self.dynamic_type_info( + projection.resolved_type, + base_lvalue.type_info.type_module_path, + ), })); } @@ -317,88 +342,68 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { &mut self, object: DynamicLvalue<'ctx>, ) -> Result>> { - let module_path = object.type_info.module_path.clone(); - let type_id = object.type_info.type_id; - let object_type = self.complete_dynamic_member_element_type( - object.type_info.dwarf_type, - module_path.as_deref(), + let fallback_module_path = object.type_info.type_module_path; + let mut resolved_type = object.type_info.resolved_type; + resolved_type.summary = self.complete_dynamic_member_element_type( + resolved_type.summary, + fallback_module_path.as_deref(), ); - match ghostscope_dwarf::strip_type_aliases(&object_type) { - DwarfType::StructType { .. } | DwarfType::UnionType { .. } => Ok(Some(DynamicLvalue { + let object_type_info = self.dynamic_type_info(resolved_type, fallback_module_path); + + if matches!( + ghostscope_dwarf::strip_type_aliases(&object_type_info.resolved_type.summary), + DwarfType::StructType { .. } | DwarfType::UnionType { .. } + ) { + return Ok(Some(DynamicLvalue { address: object.address, - type_info: DynamicTypeInfo { - dwarf_type: object_type, - module_path, - type_id, - }, - })), - DwarfType::PointerType { target_type, .. } => { - let pointer_value = - self.read_dynamic_address_value(object.address, &object_type)?; - let pointer_value = match pointer_value { - BasicValueEnum::IntValue(value) => { - self.normalize_int_to_i64(value, "dynamic_member_pointer_i64")? - } - BasicValueEnum::PointerValue(value) => self - .builder - .build_ptr_to_int( - value, - self.context.i64_type(), - "dynamic_member_pointer_ptr", - ) - .map_err(|err| CodeGenError::Builder(err.to_string()))?, - _ => { - return Err(CodeGenError::TypeError( - "dynamic member pointer base did not compile to an address".to_string(), - )) - } - }; - let target_type = self.complete_dynamic_member_element_type( - target_type.as_ref().clone(), - module_path.as_deref(), - ); - Ok(Some(DynamicLvalue { - address: RuntimeAddress::available(pointer_value, self.context), - type_info: DynamicTypeInfo { - dwarf_type: target_type, - module_path, - type_id, - }, - })) - } - _ => Ok(None), + type_info: object_type_info, + })); + } + if !matches!( + ghostscope_dwarf::strip_type_aliases(&object_type_info.resolved_type.summary), + DwarfType::PointerType { .. } + ) { + return Ok(None); } - } - fn dynamic_member_offset_and_type( - &self, - aggregate: &DynamicTypeInfo, - field: &str, - ) -> Result<(u64, DwarfType)> { - let aggregate_type = self.complete_dynamic_member_element_type( - aggregate.dwarf_type.clone(), - aggregate.module_path.as_deref(), - ); - match ghostscope_dwarf::member_layout(&aggregate_type, field) { - Ok(layout) => Ok((layout.offset, layout.member_type)), - Err(err @ TypeLayoutError::UnknownMember { .. }) => { - Err(CodeGenError::DwarfError(err.to_string())) + let projection = + self.project_dynamic_type(&object_type_info, &VariableAccessSegment::Dereference)?; + let pointer_value = self + .read_dynamic_address_value(object.address, &object_type_info.resolved_type.summary)?; + let pointer_value = match pointer_value { + BasicValueEnum::IntValue(value) => { + self.normalize_int_to_i64(value, "dynamic_member_pointer_i64")? } - Err(err @ TypeLayoutError::InvalidMemberBase { .. }) => { - Err(CodeGenError::TypeError(err.to_string())) + BasicValueEnum::PointerValue(value) => self + .builder + .build_ptr_to_int(value, self.context.i64_type(), "dynamic_member_pointer_ptr") + .map_err(|err| CodeGenError::Builder(err.to_string()))?, + _ => { + return Err(CodeGenError::TypeError( + "dynamic member pointer base did not compile to an address".to_string(), + )) } - } + }; + let mut target_type_info = + self.dynamic_type_info(projection.resolved_type, object_type_info.type_module_path); + target_type_info.resolved_type.summary = self.complete_dynamic_member_element_type( + target_type_info.resolved_type.summary, + target_type_info.type_module_path.as_deref(), + ); + Ok(Some(DynamicLvalue { + address: RuntimeAddress::available(pointer_value, self.context), + type_info: target_type_info, + })) } - fn dynamic_tuple_offset_and_type( + pub(super) fn project_dynamic_type( &self, - aggregate: &DynamicTypeInfo, - index: u32, - ) -> Result<(u64, DwarfType)> { - let aggregate_type = self.complete_dynamic_member_element_type( - aggregate.dwarf_type.clone(), - aggregate.module_path.as_deref(), - ); + current: &DynamicTypeInfo, + segment: &VariableAccessSegment, + ) -> Result { + if let Some(projection) = current.resolved_type.project_structural(segment) { + return Ok(projection); + } let analyzer = self .process_analyzer .ok_or_else(|| CodeGenError::DwarfError("No DWARF analyzer available".to_string()))?; @@ -406,33 +411,36 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { .current_compile_time_context .as_ref() .map(|context| PathBuf::from(&context.module_path)); - let module_path = aggregate - .module_path + let module_path = current + .type_module_path .as_deref() - .or(fallback_module_path.as_deref()) - .ok_or_else(|| { - CodeGenError::DwarfError("Tuple projection has no originating module".to_string()) - })?; - let layout = match aggregate.type_id { - Some(type_id) => analyzer.tuple_member_layout(type_id, &aggregate_type, index), - None => analyzer.tuple_member_layout_in_module(module_path, &aggregate_type, index), - } - .map_err(|error| CodeGenError::DwarfError(error.to_string()))?; - Ok((layout.offset, layout.member_type)) + .or(fallback_module_path.as_deref()); + analyzer + .project_resolved_type(¤t.resolved_type, segment, module_path) + .map_err(|error| CodeGenError::DwarfError(error.to_string())) } - fn project_dynamic_type_id( + fn dynamic_type_info_from_plan( &self, - current: Option, - segment: &VariableAccessSegment, - ) -> Result> { - let Some(current) = current else { + plan: &VariableReadPlan, + ) -> Result> { + let Some(summary) = plan.dwarf_type.clone() else { return Ok(None); }; - self.process_analyzer - .ok_or_else(|| CodeGenError::DwarfError("No DWARF analyzer available".to_string()))? - .project_type_id(current, segment) - .map_err(|error| CodeGenError::DwarfError(error.to_string())) + let resolved_type = if let Some(analyzer) = self.process_analyzer { + analyzer + .resolved_type_for_plan(plan) + .map_err(|error| CodeGenError::DwarfError(error.to_string()))? + .unwrap_or_else(|| { + ResolvedType::new(summary, TypeIdentity::from_dwarf(plan.type_id), None) + }) + } else { + ResolvedType::new(summary, TypeIdentity::from_dwarf(plan.type_id), None) + }; + Ok(Some(self.dynamic_type_info( + resolved_type, + plan.module_path.clone(), + ))) } fn dynamic_array_base_from_plan( @@ -442,21 +450,23 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { status_ptr: Option>, static_index: i64, ) -> Result<(IndexableElementInfo, RuntimeAddress<'ctx>, i64)> { - let module_path = array_plan.module_path.clone(); - let array_type = array_plan.dwarf_type.as_ref().ok_or_else(|| { - CodeGenError::DwarfError("Array expression has no DWARF type information".to_string()) - })?; - let element_type_id = self - .project_dynamic_type_id(array_plan.type_id, &VariableAccessSegment::ArrayIndex(0))?; - let element_info = Self::indexable_info_from_type(array_type, module_path, element_type_id) + let array_type_info = self + .dynamic_type_info_from_plan(array_plan)? + .ok_or_else(|| { + CodeGenError::DwarfError( + "Array expression has no DWARF type information".to_string(), + ) + })?; + let element_info = self + .project_indexable_type(&array_type_info)? .ok_or_else(|| { CodeGenError::TypeError(format!( "dynamic array index requires array or pointer type, got '{}'", - array_type.type_name() + array_type_info.resolved_type.summary.type_name() )) })?; - match ghostscope_dwarf::strip_type_aliases(array_type) { + match ghostscope_dwarf::strip_type_aliases(&array_type_info.resolved_type.summary) { DwarfType::ArrayType { .. } => { let base_address = self.variable_read_plan_to_runtime_address(array_plan, pc_address, status_ptr)?; @@ -473,7 +483,7 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { )?; Ok((element_info, base_address, static_index)) } - _ => unreachable!("indexable_info_from_type accepts only array or pointer types"), + _ => unreachable!("project_indexable_type accepts only array or pointer types"), } } @@ -542,30 +552,22 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { } else if let Some(array_lvalue) = self.dynamic_lvalue_address_and_type(&expanded_array_expr)? { - let module_path = array_lvalue.type_info.module_path.clone(); - let element_type_id = self.project_dynamic_type_id( - array_lvalue.type_info.type_id, - &VariableAccessSegment::ArrayIndex(0), - )?; - let element_info = Self::indexable_info_from_type( - &array_lvalue.type_info.dwarf_type, - module_path, - element_type_id, - ) - .ok_or_else(|| { - CodeGenError::TypeError(format!( - "dynamic array index requires array or pointer type, got '{}'", - array_lvalue.type_info.dwarf_type.type_name() - )) - })?; + let element_info = self + .project_indexable_type(&array_lvalue.type_info)? + .ok_or_else(|| { + CodeGenError::TypeError(format!( + "dynamic array index requires array or pointer type, got '{}'", + array_lvalue.type_info.resolved_type.summary.type_name() + )) + })?; match ghostscope_dwarf::strip_type_aliases( - &array_lvalue.type_info.dwarf_type, + &array_lvalue.type_info.resolved_type.summary, ) { DwarfType::ArrayType { .. } => (element_info, array_lvalue.address, 0), DwarfType::PointerType { .. } => { let pointer_value = self.read_dynamic_address_value( array_lvalue.address, - &array_lvalue.type_info.dwarf_type, + &array_lvalue.type_info.resolved_type.summary, )?; let base_address = self.compiled_pointer_value_to_runtime_address( pointer_value, @@ -576,7 +578,7 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { (element_info, base_address, 0) } _ => unreachable!( - "indexable_info_from_type accepts only array or pointer types" + "project_indexable_type accepts only array or pointer types" ), } } else { @@ -633,11 +635,7 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { Ok(Some(DynamicLvalue { address: base_address.with_value(element_address), - type_info: DynamicTypeInfo { - dwarf_type: element_info.element_type, - module_path: element_info.module_path, - type_id: element_info.type_id, - }, + type_info: element_info.type_info, })) } @@ -655,14 +653,8 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { } if let Some(plan) = self.query_dwarf_for_complex_expr(&expanded)? { - if let Some(dwarf_type) = plan.dwarf_type.as_ref() { - let element_type_id = self - .project_dynamic_type_id(plan.type_id, &VariableAccessSegment::ArrayIndex(0))?; - if let Some(info) = Self::indexable_info_from_type( - dwarf_type, - plan.module_path.clone(), - element_type_id, - ) { + if let Some(type_info) = self.dynamic_type_info_from_plan(&plan)? { + if let Some(info) = self.project_indexable_type(&type_info)? { return Ok(Some(info)); } } @@ -672,16 +664,8 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { self.pointer_arithmetic_parts_expanding_aliases(&expanded)? { if let Some(plan) = self.query_dwarf_for_complex_expr(&base_expr)? { - if let Some(dwarf_type) = plan.dwarf_type.as_ref() { - let element_type_id = self.project_dynamic_type_id( - plan.type_id, - &VariableAccessSegment::ArrayIndex(0), - )?; - if let Some(info) = Self::indexable_info_from_type( - dwarf_type, - plan.module_path.clone(), - element_type_id, - ) { + if let Some(type_info) = self.dynamic_type_info_from_plan(&plan)? { + if let Some(info) = self.project_indexable_type(&type_info)? { return Ok(Some(info)); } } @@ -734,7 +718,7 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { address: RuntimeAddress<'ctx>, dwarf_type: &DwarfType, ) -> Result> { - if ghostscope_dwarf::is_c_aggregate_type(dwarf_type) { + if ghostscope_dwarf::is_aggregate_type(dwarf_type) { let ptr_ty = self.context.ptr_type(AddressSpace::default()); let as_ptr = self .builder diff --git a/ghostscope-compiler/src/ebpf/expression/mod.rs b/ghostscope-compiler/src/ebpf/expression/mod.rs index fa7672d0..e4c81d25 100644 --- a/ghostscope-compiler/src/ebpf/expression/mod.rs +++ b/ghostscope-compiler/src/ebpf/expression/mod.rs @@ -5,7 +5,7 @@ use super::context::{CodeGenError, EbpfContext, Result, RuntimeAddress}; use super::expression_plan::{BinaryEmitKind, BuiltinCallPlan}; use crate::script::Expr; -use ghostscope_dwarf::{CIntegerComparisonType, TypeId, TypeInfo as DwarfType}; +use ghostscope_dwarf::{CIntegerComparisonType, ResolvedType}; use inkwell::values::BasicValueEnum; use inkwell::AddressSpace; @@ -22,9 +22,8 @@ use tracing::debug; #[derive(Clone)] pub(super) struct DynamicTypeInfo { - pub(super) dwarf_type: DwarfType, - pub(super) module_path: Option, - pub(super) type_id: Option, + pub(super) resolved_type: ResolvedType, + pub(super) type_module_path: Option, } pub(super) struct DynamicLvalue<'ctx> { @@ -33,10 +32,8 @@ pub(super) struct DynamicLvalue<'ctx> { } struct IndexableElementInfo { - element_type: DwarfType, + type_info: DynamicTypeInfo, stride: u64, - module_path: Option, - type_id: Option, } impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { diff --git a/ghostscope-compiler/src/ebpf/expression/runtime_address.rs b/ghostscope-compiler/src/ebpf/expression/runtime_address.rs index 5e55ad78..834c7abd 100644 --- a/ghostscope-compiler/src/ebpf/expression/runtime_address.rs +++ b/ghostscope-compiler/src/ebpf/expression/runtime_address.rs @@ -155,7 +155,7 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { .ok() .flatten() .and_then(|var| var.dwarf_type) - .is_some_and(|ty| ghostscope_dwarf::is_c_pointer_or_array_type(&ty)) + .is_some_and(|ty| ghostscope_dwarf::is_pointer_or_array_type(&ty)) { return Ok(true); } @@ -175,7 +175,7 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { .ok() .flatten() .and_then(|var| var.dwarf_type) - .is_some_and(|ty| ghostscope_dwarf::is_c_pointer_or_array_type(&ty))) + .is_some_and(|ty| ghostscope_dwarf::is_pointer_or_array_type(&ty))) } pub(super) fn expands_to_nonliteral_pointer_arithmetic(&mut self, expr: &Expr) -> Result { diff --git a/ghostscope-dwarf/src/analyzer/type_context.rs b/ghostscope-dwarf/src/analyzer/type_context.rs index cb1c4484..1b8bd317 100644 --- a/ghostscope-dwarf/src/analyzer/type_context.rs +++ b/ghostscope-dwarf/src/analyzer/type_context.rs @@ -1,8 +1,9 @@ use super::DwarfAnalyzer; use crate::{ - member_layout, semantics::PlanError, strip_type_aliases, CompilationUnitMetadata, CuId, - MemberLayout, ModuleId, PcContext, Result, SemanticType, TypeId, TypeInfo, TypeLayoutError, - TypeOrigin, VariableAccessSegment, VariableReadPlan, + indexable_element_layout, member_layout, semantics::PlanError, strip_type_aliases, + CompilationUnitMetadata, CuId, MemberLayout, ModuleId, PcContext, ResolvedType, Result, + SemanticType, TypeId, TypeIdentity, TypeInfo, TypeLayoutError, TypeOrigin, TypeProjection, + TypeProjectionLayout, VariableAccessSegment, VariableReadPlan, }; use std::path::Path; @@ -54,6 +55,12 @@ impl DwarfAnalyzer { Ok(Some(SemanticType::new(summary, plan.type_id, origin))) } + /// Combine a read plan's physical type with its stable identity and origin. + pub fn resolved_type_for_plan(&self, plan: &VariableReadPlan) -> Result> { + self.semantic_type_for_plan(plan) + .map(|semantic| semantic.map(ResolvedType::from_semantic_type)) + } + /// Plan constant pointer arithmetic while preserving the projected type identity. pub fn plan_pointer_element_index( &self, @@ -101,6 +108,142 @@ impl DwarfAnalyzer { aggregate_type: &TypeInfo, index: u32, ) -> Result { + let type_id = + self.tuple_aggregate_type_id_in_module(module_path.as_ref(), aggregate_type, index)?; + self.tuple_member_layout(type_id, aggregate_type, index) + } + + /// Project physical layout, type summary, identity, and origin as one + /// operation so callers cannot accidentally advance only part of the type. + pub fn project_resolved_type( + &self, + current: &ResolvedType, + segment: &VariableAccessSegment, + type_module_path: Option<&Path>, + ) -> Result { + if let Some(projection) = current.project_structural(segment) { + return Ok(projection); + } + + let (layout, summary, identity) = match segment { + VariableAccessSegment::Dereference => { + let TypeInfo::PointerType { target_type, .. } = + strip_type_aliases(¤t.summary) + else { + return Err(anyhow::anyhow!( + "dereference requires pointer type, got '{}'", + current.summary.type_name() + )); + }; + ( + TypeProjectionLayout::Dereference, + target_type.as_ref().clone(), + self.project_type_identity(¤t.identity, segment)?, + ) + } + VariableAccessSegment::ArrayIndex(_) => { + let element = indexable_element_layout(¤t.summary).ok_or_else(|| { + anyhow::anyhow!( + "array index requires array or pointer type, got '{}'", + current.summary.type_name() + ) + })?; + ( + TypeProjectionLayout::Element { + stride: element.stride, + }, + element.element_type, + self.project_type_identity(¤t.identity, segment)?, + ) + } + VariableAccessSegment::Field(field) => { + let member = member_layout(¤t.summary, field)?; + ( + TypeProjectionLayout::Member { + offset: member.offset, + }, + member.member_type, + self.project_type_identity(¤t.identity, segment)?, + ) + } + VariableAccessSegment::TupleIndex(index) => { + let aggregate_id = match current.identity.layout_dwarf_id() { + Some(type_id) => type_id, + None => { + let module_path = type_module_path + .ok_or(PlanError::TupleIndexMissingTypeIdentity { index: *index })?; + self.tuple_aggregate_type_id_in_module( + module_path, + ¤t.summary, + *index, + )? + } + }; + let member = self.tuple_member_layout(aggregate_id, ¤t.summary, *index)?; + let identity = self + .project_type_id(aggregate_id, segment)? + .map(TypeIdentity::Dwarf) + .unwrap_or(TypeIdentity::Unknown); + ( + TypeProjectionLayout::Member { + offset: member.offset, + }, + member.member_type, + identity, + ) + } + }; + let origin = match identity.underlying_dwarf_id() { + Some(type_id) => self.type_origin(type_id)?, + None => None, + }; + + Ok(TypeProjection { + layout, + resolved_type: ResolvedType::new(summary, identity, origin), + }) + } + + fn project_type_id( + &self, + current: TypeId, + segment: &VariableAccessSegment, + ) -> Result> { + let layout_segment = self.layout_access_segment(Some(current), segment)?; + self.projected_type_id(current, &layout_segment) + } + + fn project_type_identity( + &self, + current: &TypeIdentity, + segment: &VariableAccessSegment, + ) -> Result { + if let Some(projected) = current.project_structural(segment) { + return Ok(projected); + } + match current { + TypeIdentity::Dwarf(type_id) => Ok(self + .project_type_id(*type_id, segment)? + .map(TypeIdentity::Dwarf) + .unwrap_or(TypeIdentity::Unknown)), + TypeIdentity::Synthetic { + kind: crate::SyntheticTypeKind::Qualified, + inner, + } => self.project_type_identity(inner, segment), + TypeIdentity::Synthetic { + kind: crate::SyntheticTypeKind::Pointer | crate::SyntheticTypeKind::Array, + .. + } + | TypeIdentity::Unknown => Ok(TypeIdentity::Unknown), + } + } + + fn tuple_aggregate_type_id_in_module( + &self, + module_path: &Path, + aggregate_type: &TypeInfo, + index: u32, + ) -> Result { let module_path = self .loaded_module_path_for(module_path) .ok_or_else(|| anyhow::anyhow!("Module is not loaded for tuple projection"))?; @@ -117,22 +260,10 @@ impl DwarfAnalyzer { .into()) } }; - let type_id = self - .modules + self.modules .get(module_path) .and_then(|module_data| module_data.aggregate_type_id_by_name(module, type_name)) - .ok_or(PlanError::TupleIndexMissingTypeIdentity { index })?; - self.tuple_member_layout(type_id, aggregate_type, index) - } - - /// Project a stable type identity through one source-level access segment. - pub fn project_type_id( - &self, - current: TypeId, - segment: &VariableAccessSegment, - ) -> Result> { - let layout_segment = self.layout_access_segment(Some(current), segment)?; - self.projected_type_id(current, &layout_segment) + .ok_or_else(|| PlanError::TupleIndexMissingTypeIdentity { index }.into()) } pub(super) fn projected_type_id( diff --git a/ghostscope-dwarf/src/analyzer/type_lookup.rs b/ghostscope-dwarf/src/analyzer/type_lookup.rs index 47777f4a..632feb0b 100644 --- a/ghostscope-dwarf/src/analyzer/type_lookup.rs +++ b/ghostscope-dwarf/src/analyzer/type_lookup.rs @@ -1,5 +1,10 @@ use super::DwarfAnalyzer; -use crate::semantics::{strip_type_aliases, VariableReadPlan}; +use crate::{ + semantics::{ + strip_type_aliases, ResolvedType, SemanticType, SyntheticTypeKind, VariableReadPlan, + }, + type_syntax::c_style as type_spec, +}; use std::fmt; use std::path::{Path, PathBuf}; @@ -29,7 +34,12 @@ impl std::error::Error for TypeLookupAmbiguity {} impl DwarfAnalyzer { pub fn resolve_builtin_type_spec(type_spec: &str) -> Option { - resolve_type_spec_with(type_spec, |_| None) + Self::resolve_builtin_c_style_semantic_type_spec(type_spec).map(|resolved| resolved.summary) + } + + /// Resolve the C-style type grammar accepted by GhostScope's `cast` DSL. + pub fn resolve_builtin_c_style_semantic_type_spec(type_spec: &str) -> Option { + resolve_c_style_semantic_type_spec_with(type_spec, |_| None) } pub fn resolve_type_spec_in_module>( @@ -37,10 +47,19 @@ impl DwarfAnalyzer { module_path: P, type_spec: &str, ) -> Option { + self.resolve_c_style_semantic_type_spec_in_module(module_path, type_spec) + .map(|resolved| resolved.summary) + } + + pub fn resolve_c_style_semantic_type_spec_in_module>( + &self, + module_path: P, + type_spec: &str, + ) -> Option { let module_path = module_path.as_ref().to_path_buf(); - resolve_type_spec_with(type_spec, |name| { - self.resolve_named_type_in_module(&module_path, name) - .or_else(|| self.resolve_named_type(name)) + resolve_c_style_semantic_type_spec_with(type_spec, |name| { + self.resolve_named_semantic_type_in_module(&module_path, name) + .or_else(|| self.resolve_named_semantic_type(name)) }) } @@ -49,92 +68,134 @@ impl DwarfAnalyzer { module_path: P, type_spec: &str, ) -> std::result::Result, TypeLookupAmbiguity> { + self.try_resolve_c_style_semantic_type_spec_in_module(module_path, type_spec) + .map(|resolved| resolved.map(|resolved| resolved.summary)) + } + + pub fn try_resolve_c_style_semantic_type_spec_in_module>( + &self, + module_path: P, + type_spec: &str, + ) -> std::result::Result, TypeLookupAmbiguity> { let module_path = module_path.as_ref().to_path_buf(); - try_resolve_type_spec_with(type_spec, |name| { - if let Some(ty) = self.resolve_named_type_in_module(&module_path, name) { + try_resolve_c_style_semantic_type_spec_with(type_spec, |name| { + if let Some(ty) = self.resolve_named_semantic_type_in_module(&module_path, name) { return Ok(Some(ty)); } - self.resolve_unique_named_type_outside_module(&module_path, name) + self.resolve_unique_named_semantic_type_outside_module(&module_path, name) }) } pub fn resolve_type_spec(&self, type_spec: &str) -> Option { - resolve_type_spec_with(type_spec, |name| self.resolve_named_type(name)) + self.resolve_c_style_semantic_type_spec(type_spec) + .map(|resolved| resolved.summary) + } + + pub fn resolve_c_style_semantic_type_spec(&self, type_spec: &str) -> Option { + resolve_c_style_semantic_type_spec_with(type_spec, |name| { + self.resolve_named_semantic_type(name) + }) } pub fn try_resolve_type_spec( &self, type_spec: &str, ) -> std::result::Result, TypeLookupAmbiguity> { - try_resolve_type_spec_with(type_spec, |name| self.resolve_unique_named_type(name)) + self.try_resolve_c_style_semantic_type_spec(type_spec) + .map(|resolved| resolved.map(|resolved| resolved.summary)) } - fn resolve_type_shallow_by_name_in_module_with_tags>( + pub fn try_resolve_c_style_semantic_type_spec( + &self, + type_spec: &str, + ) -> std::result::Result, TypeLookupAmbiguity> { + try_resolve_c_style_semantic_type_spec_with(type_spec, |name| { + self.resolve_unique_named_semantic_type(name) + }) + } + + fn resolve_semantic_type_shallow_by_name_in_module_with_tags>( &self, module_path: P, name: &str, tags: &[gimli::DwTag], - ) -> Option { - let path_buf = module_path.as_ref().to_path_buf(); - self.modules - .get(&path_buf) - .and_then(|module_data| module_data.resolve_type_shallow_by_name_with_tags(name, tags)) + ) -> Option { + let module_path = self.loaded_module_path_for(module_path)?.clone(); + let module = self.module_id_for_path(&module_path)?; + let (summary, loc) = self + .modules + .get(&module_path)? + .resolve_type_shallow_by_name_with_tags_and_loc(name, tags)?; + let id = loc.type_id(module); + Some(SemanticType::new( + summary, + Some(id), + self.type_origin(id).ok().flatten(), + )) } - fn resolve_type_shallow_by_name_with_tags( + fn resolve_semantic_type_shallow_by_name_with_tags( &self, name: &str, tags: &[gimli::DwTag], - ) -> Option { - self.modules - .values() - .find_map(|module_data| module_data.resolve_type_shallow_by_name_with_tags(name, tags)) + ) -> Option { + self.sorted_module_paths() + .into_iter() + .find_map(|module_path| { + self.resolve_semantic_type_shallow_by_name_in_module_with_tags( + module_path, + name, + tags, + ) + }) } - fn resolve_named_type_in_module( + fn resolve_named_semantic_type_in_module( &self, module_path: &Path, name: &str, - ) -> Option { + ) -> Option { let tags = [ gimli::constants::DW_TAG_structure_type, gimli::constants::DW_TAG_class_type, gimli::constants::DW_TAG_union_type, gimli::constants::DW_TAG_enumeration_type, ]; - self.resolve_type_shallow_by_name_in_module_with_tags(module_path, name, &tags) + self.resolve_semantic_type_shallow_by_name_in_module_with_tags(module_path, name, &tags) } - fn resolve_named_type(&self, name: &str) -> Option { + fn resolve_named_semantic_type(&self, name: &str) -> Option { let tags = [ gimli::constants::DW_TAG_structure_type, gimli::constants::DW_TAG_class_type, gimli::constants::DW_TAG_union_type, gimli::constants::DW_TAG_enumeration_type, ]; - self.resolve_type_shallow_by_name_with_tags(name, &tags) + self.resolve_semantic_type_shallow_by_name_with_tags(name, &tags) } - fn resolve_unique_named_type_outside_module( + fn resolve_unique_named_semantic_type_outside_module( &self, module_path: &Path, name: &str, - ) -> std::result::Result, TypeLookupAmbiguity> { - self.resolve_unique_named_type_with_filter(name, |candidate| candidate != module_path) + ) -> std::result::Result, TypeLookupAmbiguity> { + self.resolve_unique_named_semantic_type_with_filter(name, |candidate| { + candidate != module_path + }) } - fn resolve_unique_named_type( + fn resolve_unique_named_semantic_type( &self, name: &str, - ) -> std::result::Result, TypeLookupAmbiguity> { - self.resolve_unique_named_type_with_filter(name, |_| true) + ) -> std::result::Result, TypeLookupAmbiguity> { + self.resolve_unique_named_semantic_type_with_filter(name, |_| true) } - fn resolve_unique_named_type_with_filter( + fn resolve_unique_named_semantic_type_with_filter( &self, name: &str, include_module: impl Fn(&Path) -> bool, - ) -> std::result::Result, TypeLookupAmbiguity> { + ) -> std::result::Result, TypeLookupAmbiguity> { let tags = [ gimli::constants::DW_TAG_structure_type, gimli::constants::DW_TAG_class_type, @@ -145,9 +206,8 @@ impl DwarfAnalyzer { .modules .iter() .filter(|(path, _)| include_module(path.as_path())) - .filter_map(|(path, module_data)| { - module_data - .resolve_type_shallow_by_name_with_tags(name, &tags) + .filter_map(|(path, _)| { + self.resolve_semantic_type_shallow_by_name_in_module_with_tags(path, name, &tags) .map(|ty| (path.clone(), ty)) }) .collect::>(); @@ -373,7 +433,7 @@ impl DwarfAnalyzer { module_path: P, name: &str, ) -> Option { - self.resolve_type_shallow_by_name_in_module_with_tags( + self.resolve_semantic_type_shallow_by_name_in_module_with_tags( module_path, name, &[ @@ -381,17 +441,19 @@ impl DwarfAnalyzer { gimli::constants::DW_TAG_class_type, ], ) + .map(|semantic| semantic.summary) } /// Resolve struct/class by name (shallow) across modules (first match) pub fn resolve_struct_type_shallow_by_name(&self, name: &str) -> Option { - self.resolve_type_shallow_by_name_with_tags( + self.resolve_semantic_type_shallow_by_name_with_tags( name, &[ gimli::constants::DW_TAG_structure_type, gimli::constants::DW_TAG_class_type, ], ) + .map(|semantic| semantic.summary) } /// Resolve union by name (shallow) in a specific module @@ -400,16 +462,21 @@ impl DwarfAnalyzer { module_path: P, name: &str, ) -> Option { - self.resolve_type_shallow_by_name_in_module_with_tags( + self.resolve_semantic_type_shallow_by_name_in_module_with_tags( module_path, name, &[gimli::constants::DW_TAG_union_type], ) + .map(|semantic| semantic.summary) } /// Resolve union by name (shallow) across modules (first match) pub fn resolve_union_type_shallow_by_name(&self, name: &str) -> Option { - self.resolve_type_shallow_by_name_with_tags(name, &[gimli::constants::DW_TAG_union_type]) + self.resolve_semantic_type_shallow_by_name_with_tags( + name, + &[gimli::constants::DW_TAG_union_type], + ) + .map(|semantic| semantic.summary) } /// Resolve enum by name (shallow) in a specific module @@ -418,144 +485,87 @@ impl DwarfAnalyzer { module_path: P, name: &str, ) -> Option { - self.resolve_type_shallow_by_name_in_module_with_tags( + self.resolve_semantic_type_shallow_by_name_in_module_with_tags( module_path, name, &[gimli::constants::DW_TAG_enumeration_type], ) + .map(|semantic| semantic.summary) } /// Resolve enum by name (shallow) across modules (first match) pub fn resolve_enum_type_shallow_by_name(&self, name: &str) -> Option { - self.resolve_type_shallow_by_name_with_tags( + self.resolve_semantic_type_shallow_by_name_with_tags( name, &[gimli::constants::DW_TAG_enumeration_type], ) + .map(|semantic| semantic.summary) } } -fn resolve_type_spec_with(type_spec: &str, mut resolve_named: F) -> Option +fn resolve_c_style_semantic_type_spec_with( + type_spec: &str, + mut resolve_named: F, +) -> Option where - F: FnMut(&str) -> Option, + F: FnMut(&str) -> Option, { - try_resolve_type_spec_with(type_spec, |name| { + try_resolve_c_style_semantic_type_spec_with(type_spec, |name| { Ok::<_, std::convert::Infallible>(resolve_named(name)) }) .ok() .flatten() } -fn try_resolve_type_spec_with( +fn try_resolve_c_style_semantic_type_spec_with( type_spec: &str, mut resolve_named: F, -) -> std::result::Result, E> +) -> std::result::Result, E> where - F: FnMut(&str) -> std::result::Result, E>, + F: FnMut(&str) -> std::result::Result, E>, { - // TODO: This parser intentionally accepts only C/C++-style type specs for now. - // Add an explicit TypeSpec/parser layer before extending this to Rust or other languages. - let mut spec = type_spec.trim(); - if spec.is_empty() { - return Ok(None); - } - - let mut arrays = Vec::new(); - while let Some((base, count)) = take_array_suffix(spec) { - arrays.push(count); - spec = base.trim_end(); - } - - let mut pointer_count = 0usize; - while let Some(base) = spec.strip_suffix('*') { - pointer_count += 1; - spec = base.trim_end(); - } - - let (qualifiers, base_spec) = strip_leading_qualifiers(spec); - let Some(mut ty) = try_resolve_base_type_spec(base_spec, &mut resolve_named)? else { + let Some(spec) = type_spec::parse(type_spec) else { return Ok(None); }; - - for qualifier in qualifiers.into_iter().rev() { - ty = crate::TypeInfo::QualifiedType { - qualifier, - underlying_type: Box::new(ty), - }; - } - - for _ in 0..pointer_count { - ty = crate::TypeInfo::PointerType { - target_type: Box::new(ty), - size: 8, - }; - } - - for count in arrays.into_iter().rev() { - let element_size = ty.size(); - let total_size = count.and_then(|count| element_size.checked_mul(count)); - ty = crate::TypeInfo::ArrayType { - element_type: Box::new(ty), - element_count: count, - total_size, - }; - } - - Ok(Some(ty)) -} - -fn take_array_suffix(spec: &str) -> Option<(&str, Option)> { - let spec = spec.trim_end(); - if !spec.ends_with(']') { - return None; - } - let open = spec.rfind('[')?; - let inside = spec[open + 1..spec.len() - 1].trim(); - let count = if inside.is_empty() { - None + let mut resolved = if let Some(builtin) = builtin_type_spec(&spec.base) { + ResolvedType::synthetic(builtin) + } else if let Some(named) = resolve_named(&spec.base)? { + ResolvedType::from_semantic_type(named) } else { - Some(inside.parse::().ok()?) + return Ok(None); }; - Some((&spec[..open], count)) -} -fn strip_leading_qualifiers(mut spec: &str) -> (Vec, &str) { - let mut qualifiers = Vec::new(); - loop { - let trimmed = spec.trim_start(); - if let Some(rest) = trimmed.strip_prefix("const ") { - qualifiers.push(crate::TypeQualifier::Const); - spec = rest; - } else if let Some(rest) = trimmed.strip_prefix("volatile ") { - qualifiers.push(crate::TypeQualifier::Volatile); - spec = rest; - } else if let Some(rest) = trimmed.strip_prefix("restrict ") { - qualifiers.push(crate::TypeQualifier::Restrict); - spec = rest; - } else { - return (qualifiers, trimmed); - } + for qualifier in spec.qualifiers.into_iter().rev() { + resolved = resolved.wrap(SyntheticTypeKind::Qualified, |underlying_type| { + crate::TypeInfo::QualifiedType { + qualifier, + underlying_type: Box::new(underlying_type), + } + }); } -} -fn try_resolve_base_type_spec( - spec: &str, - resolve_named: &mut F, -) -> std::result::Result, E> -where - F: FnMut(&str) -> std::result::Result, E>, -{ - let spec = spec.trim(); - if let Some(ty) = builtin_type_spec(spec) { - return Ok(Some(ty)); + for _ in 0..spec.pointer_count { + resolved = resolved.wrap(SyntheticTypeKind::Pointer, |target_type| { + crate::TypeInfo::PointerType { + target_type: Box::new(target_type), + size: 8, + } + }); } - for prefix in ["struct ", "class ", "union ", "enum "] { - if let Some(name) = spec.strip_prefix(prefix) { - return resolve_named(name.trim()); - } + for count in spec.arrays.into_iter().rev() { + let element_size = resolved.summary.size(); + let total_size = count.and_then(|count| element_size.checked_mul(count)); + resolved = resolved.wrap(SyntheticTypeKind::Array, |element_type| { + crate::TypeInfo::ArrayType { + element_type: Box::new(element_type), + element_count: count, + total_size, + } + }); } - resolve_named(spec) + Ok(Some(resolved)) } fn builtin_type_spec(spec: &str) -> Option { @@ -624,6 +634,21 @@ fn builtin_type_spec(spec: &str) -> Option { #[cfg(test)] mod tests { use super::*; + use crate::TypeIdentity; + + fn type_id() -> crate::TypeId { + let module = crate::ModuleId(4); + let cu = crate::CuId(8); + crate::TypeId { + module, + cu, + die: crate::DieRef { + module, + cu, + offset: 16, + }, + } + } #[test] fn resolves_builtin_pointer_and_array_specs() { @@ -658,4 +683,62 @@ mod tests { assert_eq!(ty.size(), 8); assert!(ty.is_unsigned_int()); } + + #[test] + fn preserves_named_identity_under_synthetic_type_wrappers() { + let resolved = resolve_c_style_semantic_type_spec_with("const Pair *[2]", |name| { + (name == "Pair").then(|| { + SemanticType::new( + crate::TypeInfo::StructType { + name: "Pair".to_string(), + size: 8, + members: vec![], + }, + Some(type_id()), + None, + ) + }) + }) + .expect("type spec"); + + let TypeIdentity::Synthetic { + kind: SyntheticTypeKind::Array, + inner, + } = resolved.identity + else { + panic!("expected synthetic array identity"); + }; + let TypeIdentity::Synthetic { + kind: SyntheticTypeKind::Pointer, + inner, + } = *inner + else { + panic!("expected synthetic pointer identity"); + }; + let TypeIdentity::Synthetic { + kind: SyntheticTypeKind::Qualified, + inner, + } = *inner + else { + panic!("expected synthetic qualifier identity"); + }; + assert_eq!(*inner, TypeIdentity::Dwarf(type_id())); + + let element = TypeIdentity::Synthetic { + kind: SyntheticTypeKind::Array, + inner: Box::new(TypeIdentity::synthetic( + SyntheticTypeKind::Pointer, + TypeIdentity::Dwarf(type_id()), + )), + } + .project_structural(&crate::VariableAccessSegment::ArrayIndex(0)) + .expect("synthetic array projection"); + assert!(matches!( + element, + TypeIdentity::Synthetic { + kind: SyntheticTypeKind::Pointer, + .. + } + )); + } } diff --git a/ghostscope-dwarf/src/language/rust.rs b/ghostscope-dwarf/src/language/rust/access.rs similarity index 100% rename from ghostscope-dwarf/src/language/rust.rs rename to ghostscope-dwarf/src/language/rust/access.rs diff --git a/ghostscope-dwarf/src/language/rust/mod.rs b/ghostscope-dwarf/src/language/rust/mod.rs new file mode 100644 index 00000000..0458d5d4 --- /dev/null +++ b/ghostscope-dwarf/src/language/rust/mod.rs @@ -0,0 +1,5 @@ +mod access; + +pub(super) fn resolve_tuple_index(index: u32) -> crate::VariableAccessSegment { + access::resolve_tuple_index(index) +} diff --git a/ghostscope-dwarf/src/lib.rs b/ghostscope-dwarf/src/lib.rs index 496f61da..1a81b25a 100644 --- a/ghostscope-dwarf/src/lib.rs +++ b/ghostscope-dwarf/src/lib.rs @@ -16,6 +16,7 @@ pub(crate) mod objfile; pub(crate) mod parser; pub(crate) mod path_match; pub(crate) mod semantics; +pub(crate) mod type_syntax; // Main entry point pub(crate) mod analyzer; @@ -41,20 +42,29 @@ pub use core::{ // Re-export semantic contract types. pub use semantics::{ - c_integer_comparison_type, indexable_element_layout, is_c_aggregate_type, - is_c_pointer_or_array_type, is_c_signed_integer_type, member_layout, strip_type_aliases, - usual_c_arithmetic_comparison_plan, AddressOrigin, AddressSpaceInfo, CIntegerComparisonPlan, - CIntegerComparisonType, CfaRulePlan, CompactUnwindRow, CompactUnwindStats, CompactUnwindTable, - CompilationUnitMetadata, FunctionParameter, IndexableElementLayout, InlineFrame, - LvalueAddressPlan, MemberLayout, PcContext, PcLineInfo, PcRange, PlannedAddress, - PlannedAddressKind, PlannedValue, ProducerInfo, RegisterRecoveryPlan, RuntimeComputedExpr, - RuntimeComputedKind, SemanticType, SourceLanguage, TypeLayoutError, TypeOrigin, - UnwindDiagnostic, UnwindDiagnosticKind, VariableAccessPath, VariableAccessSegment, - VariableLoweringKind, VariableLoweringPlan, VariableMaterialization, + indexable_element_layout, is_aggregate_type, is_pointer_or_array_type, member_layout, + strip_type_aliases, AddressOrigin, AddressSpaceInfo, CfaRulePlan, CompactUnwindRow, + CompactUnwindStats, CompactUnwindTable, CompilationUnitMetadata, FunctionParameter, + IndexableElementLayout, InlineFrame, LvalueAddressPlan, MemberLayout, PcContext, PcLineInfo, + PcRange, PlannedAddress, PlannedAddressKind, PlannedValue, ProducerInfo, RegisterRecoveryPlan, + ResolvedType, RuntimeComputedExpr, RuntimeComputedKind, SemanticType, SourceLanguage, + SyntheticTypeKind, TypeIdentity, TypeLayoutError, TypeOrigin, TypeProjection, + TypeProjectionLayout, UnwindDiagnostic, UnwindDiagnosticKind, VariableAccessPath, + VariableAccessSegment, VariableLoweringKind, VariableLoweringPlan, VariableMaterialization, VariableMaterializationPlan, VariablePlan, VariableQueryDiagnostic, VariableReadPlan, VisibleVariable, VisibleVariablesResult, }; +pub use semantics::{ + c_integer_comparison_type, is_c_signed_integer_type, usual_c_arithmetic_comparison_plan, + CIntegerComparisonPlan, CIntegerComparisonType, +}; + +#[deprecated(note = "use is_aggregate_type; physical aggregate layout is language-neutral")] +pub use semantics::is_aggregate_type as is_c_aggregate_type; +#[deprecated(note = "use is_pointer_or_array_type; physical type layout is language-neutral")] +pub use semantics::is_pointer_or_array_type as is_c_pointer_or_array_type; + // Re-export type definitions from protocol (avoiding circular dependencies) pub use ghostscope_protocol::{ EnumVariant, StructMember, TypeCache, TypeInfo, TypeKind, TypeQualifier, diff --git a/ghostscope-dwarf/src/objfile/variables.rs b/ghostscope-dwarf/src/objfile/variables.rs index ce3c932f..e97d8694 100644 --- a/ghostscope-dwarf/src/objfile/variables.rs +++ b/ghostscope-dwarf/src/objfile/variables.rs @@ -7,7 +7,7 @@ use crate::{ parser::ExpressionEvaluator, semantics::{ resolve_attr_with_unit_origins, resolve_name_with_origins, resolve_origin_entry, - resolve_type_ref_with_origins, FunctionParameter, InlineFrame, PcLineInfo, + resolve_type_ref_with_origins, FunctionParameter, InlineFrame, PcLineInfo, TypeLoc, VariableQueryDiagnostic, }, }; @@ -371,11 +371,11 @@ impl LoadedObjfile { Ok(vars) } - pub(crate) fn resolve_type_shallow_by_name_with_tags( + pub(crate) fn resolve_type_shallow_by_name_with_tags_and_loc( &self, name: &str, tags: &[gimli::DwTag], - ) -> Option { + ) -> Option<(crate::TypeInfo, TypeLoc)> { if let Err(error) = self.ensure_debug_info_for_type_name(name) { tracing::warn!( "Failed to load indexed DWARF for type '{}' in {}: {}", @@ -391,7 +391,14 @@ impl LoadedObjfile { .expect("type name index lock poisoned") .find_aggregate_definition(name, tag); if let Some(loc) = loc { - return self.detailed_shallow_type(loc.cu_offset, loc.die_offset); + let ty = self.detailed_shallow_type(loc.cu_offset, loc.die_offset)?; + return Some(( + ty, + TypeLoc { + cu_off: loc.cu_offset, + die_off: loc.die_offset, + }, + )); } } @@ -405,16 +412,25 @@ impl LoadedObjfile { if let Ok(header) = dwarf.unit_header(td.cu_offset) { if let Ok(unit) = dwarf.unit(header) { if let Ok(entry) = unit.entry(td.die_offset) { - if let Some(gimli::AttributeValue::UnitRef(under)) = - entry.attr_value(gimli::DW_AT_type) + if let Ok(Some(type_loc)) = + resolve_type_ref_with_origins(dwarf, &entry, &unit) { - return self.detailed_shallow_type(td.cu_offset, under); + let ty = + self.detailed_shallow_type(type_loc.cu_off, type_loc.die_off)?; + return Some((ty, type_loc)); } - return crate::parser::DetailedParser::resolve_type_shallow_at_offset( + let ty = crate::parser::DetailedParser::resolve_type_shallow_at_offset( dwarf, &unit, td.die_offset, - ); + )?; + return Some(( + ty, + TypeLoc { + cu_off: td.cu_offset, + die_off: td.die_offset, + }, + )); } } } diff --git a/ghostscope-dwarf/src/semantics/c_types.rs b/ghostscope-dwarf/src/semantics/c_integer.rs similarity index 50% rename from ghostscope-dwarf/src/semantics/c_types.rs rename to ghostscope-dwarf/src/semantics/c_integer.rs index dffb522c..9ac64adb 100644 --- a/ghostscope-dwarf/src/semantics/c_types.rs +++ b/ghostscope-dwarf/src/semantics/c_integer.rs @@ -1,7 +1,7 @@ -//! C type semantics derived from DWARF type information. +//! C-compatible integer rules used by the GhostScope expression language. //! -//! This module keeps language-level type classification close to the DWARF -//! semantic layer so compiler backends do not need to duplicate C rules. +//! These are script evaluation semantics over DWARF integer representations, +//! not dispatch rules for C compilation units. use crate::TypeInfo; @@ -17,32 +17,6 @@ pub struct CIntegerComparisonPlan { pub is_unsigned: bool, } -#[derive(Clone, Debug, PartialEq)] -pub struct MemberLayout { - pub offset: u64, - pub member_type: TypeInfo, -} - -#[derive(Clone, Debug, PartialEq)] -pub struct IndexableElementLayout { - pub element_type: TypeInfo, - pub stride: u64, -} - -#[derive(Debug, thiserror::Error)] -pub enum TypeLayoutError { - #[error("Unknown member '{field}' in {kind} '{type_name}' (known members: {members})")] - UnknownMember { - kind: &'static str, - type_name: String, - field: String, - members: String, - }, - - #[error("member access requires struct or union type, got '{type_name}'")] - InvalidMemberBase { type_name: String }, -} - impl CIntegerComparisonType { pub fn promoted(self) -> Self { if self.size < 4 { @@ -63,71 +37,6 @@ impl CIntegerComparisonType { } } -pub fn strip_type_aliases(mut ty: &TypeInfo) -> &TypeInfo { - while let TypeInfo::TypedefType { - underlying_type, .. - } - | TypeInfo::QualifiedType { - underlying_type, .. - } = ty - { - ty = underlying_type.as_ref(); - } - ty -} - -pub fn is_c_aggregate_type(ty: &TypeInfo) -> bool { - matches!( - strip_type_aliases(ty), - TypeInfo::StructType { .. } | TypeInfo::UnionType { .. } | TypeInfo::ArrayType { .. } - ) -} - -pub fn is_c_pointer_or_array_type(ty: &TypeInfo) -> bool { - matches!( - strip_type_aliases(ty), - TypeInfo::PointerType { .. } | TypeInfo::ArrayType { .. } - ) -} - -pub fn member_layout(ty: &TypeInfo, field: &str) -> Result { - match strip_type_aliases(ty) { - TypeInfo::StructType { name, members, .. } => members - .iter() - .find(|member| member.name == field) - .map(|member| MemberLayout { - offset: member.offset, - member_type: member.member_type.clone(), - }) - .ok_or_else(|| unknown_member_error("struct", name, field, members)), - TypeInfo::UnionType { name, members, .. } => members - .iter() - .find(|member| member.name == field) - .map(|member| MemberLayout { - offset: member.offset, - member_type: member.member_type.clone(), - }) - .ok_or_else(|| unknown_member_error("union", name, field, members)), - other => Err(TypeLayoutError::InvalidMemberBase { - type_name: other.type_name(), - }), - } -} - -pub fn indexable_element_layout(ty: &TypeInfo) -> Option { - match strip_type_aliases(ty) { - TypeInfo::ArrayType { element_type, .. } => Some(IndexableElementLayout { - element_type: element_type.as_ref().clone(), - stride: element_type.size().max(1), - }), - TypeInfo::PointerType { target_type, .. } => Some(IndexableElementLayout { - element_type: target_type.as_ref().clone(), - stride: target_type.size().max(1), - }), - _ => None, - } -} - pub fn c_integer_comparison_type(ty: &TypeInfo) -> Option { match ty { TypeInfo::BaseType { encoding, size, .. } => { @@ -136,14 +45,10 @@ pub fn c_integer_comparison_type(ty: &TypeInfo) -> Option TypeLayoutError { - let mut member_names = members - .iter() - .map(|member| member.name.clone()) - .collect::>(); - member_names.sort(); - member_names.dedup(); - let list = if member_names.is_empty() { - "".to_string() - } else { - member_names.join(", ") - }; - - TypeLayoutError::UnknownMember { - kind, - type_name: type_name.to_string(), - field: field.to_string(), - members: list, - } -} - #[cfg(test)] mod tests { use super::*; - use crate::StructMember; fn int_type(name: &str, size: u64, encoding: u16) -> TypeInfo { TypeInfo::BaseType { @@ -262,76 +140,6 @@ mod tests { int_type("int", 4, crate::constants::DW_ATE_signed.0 as u16) } - #[test] - fn aggregate_classification_strips_aliases() { - let struct_type = TypeInfo::StructType { - name: "Request".to_string(), - size: 4, - members: vec![StructMember { - name: "fd".to_string(), - member_type: signed_int(), - offset: 0, - bit_offset: None, - bit_size: None, - }], - }; - let typedef = TypeInfo::TypedefType { - name: "request_t".to_string(), - underlying_type: Box::new(TypeInfo::QualifiedType { - qualifier: crate::TypeQualifier::Const, - underlying_type: Box::new(struct_type), - }), - }; - - assert!(is_c_aggregate_type(&typedef)); - } - - #[test] - fn pointer_or_array_classification_strips_aliases() { - let array_type = TypeInfo::ArrayType { - element_type: Box::new(signed_int()), - element_count: Some(4), - total_size: Some(16), - }; - let typedef = TypeInfo::TypedefType { - name: "int_array_t".to_string(), - underlying_type: Box::new(array_type), - }; - - assert!(is_c_pointer_or_array_type(&typedef)); - } - - #[test] - fn member_and_index_layout_strip_aliases() { - let signed_int = signed_int(); - let struct_type = TypeInfo::StructType { - name: "Request".to_string(), - size: 16, - members: vec![StructMember { - name: "fd".to_string(), - member_type: signed_int.clone(), - offset: 8, - bit_offset: None, - bit_size: None, - }], - }; - let qualified_struct = TypeInfo::QualifiedType { - qualifier: crate::TypeQualifier::Const, - underlying_type: Box::new(struct_type), - }; - - let layout = member_layout(&qualified_struct, "fd").expect("member layout"); - assert_eq!(layout.offset, 8); - assert_eq!(layout.member_type, signed_int.clone()); - - let pointer_type = TypeInfo::PointerType { - target_type: Box::new(signed_int), - size: 8, - }; - let element = indexable_element_layout(&pointer_type).expect("pointer element layout"); - assert_eq!(element.stride, 4); - } - #[test] fn integer_comparison_type_handles_enums_and_bitfields() { let enum_type = TypeInfo::EnumType { diff --git a/ghostscope-dwarf/src/semantics/mod.rs b/ghostscope-dwarf/src/semantics/mod.rs index d53ba0d2..4fa1d1fc 100644 --- a/ghostscope-dwarf/src/semantics/mod.rs +++ b/ghostscope-dwarf/src/semantics/mod.rs @@ -1,6 +1,7 @@ -pub mod c_types; +pub mod c_integer; pub mod pc_context; pub mod type_context; +pub mod type_layout; pub mod unwind_plan; pub mod variable_plan; @@ -8,11 +9,9 @@ pub(crate) mod origins; pub(crate) mod pc; pub(crate) mod types; -pub use c_types::{ - c_integer_comparison_type, indexable_element_layout, is_c_aggregate_type, - is_c_pointer_or_array_type, is_c_signed_integer_type, member_layout, strip_type_aliases, - usual_c_arithmetic_comparison_plan, CIntegerComparisonPlan, CIntegerComparisonType, - IndexableElementLayout, MemberLayout, TypeLayoutError, +pub use c_integer::{ + c_integer_comparison_type, is_c_signed_integer_type, usual_c_arithmetic_comparison_plan, + CIntegerComparisonPlan, CIntegerComparisonType, }; pub(crate) use origins::{ resolve_attr_with_unit_origins, resolve_name_with_origins, resolve_origin_entry, @@ -22,7 +21,12 @@ pub use pc_context::{ AddressSpaceInfo, FunctionParameter, InlineFrame, PcContext, PcLineInfo, PcRange, }; pub use type_context::{ - CompilationUnitMetadata, ProducerInfo, SemanticType, SourceLanguage, TypeOrigin, + CompilationUnitMetadata, ProducerInfo, ResolvedType, SemanticType, SourceLanguage, + SyntheticTypeKind, TypeIdentity, TypeOrigin, TypeProjection, TypeProjectionLayout, +}; +pub use type_layout::{ + indexable_element_layout, is_aggregate_type, is_pointer_or_array_type, member_layout, + strip_type_aliases, IndexableElementLayout, MemberLayout, TypeLayoutError, }; pub(crate) use types::{ resolve_type_ref_in_same_unit_with_origins, resolve_type_ref_with_origins, TypeLoc, diff --git a/ghostscope-dwarf/src/semantics/type_context.rs b/ghostscope-dwarf/src/semantics/type_context.rs index 0bf2fe33..1d669c0c 100644 --- a/ghostscope-dwarf/src/semantics/type_context.rs +++ b/ghostscope-dwarf/src/semantics/type_context.rs @@ -1,7 +1,8 @@ //! Source-language and producer context attached to DWARF-backed types. +use super::type_layout::{indexable_element_layout, strip_type_aliases}; use crate::core::{CuId, ModuleId, TypeId}; -use crate::TypeInfo; +use crate::{TypeInfo, VariableAccessSegment}; /// Normalized source-language family for semantic dispatch. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] @@ -100,9 +101,208 @@ impl SemanticType { } } +/// Identity for a type that may combine an exact DWARF DIE with script-created +/// pointer, array, or qualifier wrappers. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum TypeIdentity { + Dwarf(TypeId), + Synthetic { + kind: SyntheticTypeKind, + inner: Box, + }, + Unknown, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum SyntheticTypeKind { + Pointer, + Array, + Qualified, +} + +impl TypeIdentity { + pub fn from_dwarf(id: Option) -> Self { + id.map(Self::Dwarf).unwrap_or(Self::Unknown) + } + + pub fn synthetic(kind: SyntheticTypeKind, inner: Self) -> Self { + Self::Synthetic { + kind, + inner: Box::new(inner), + } + } + + /// Return the exact DIE for this layout, allowing only transparent + /// qualifier wrappers. + pub fn layout_dwarf_id(&self) -> Option { + match self { + Self::Dwarf(id) => Some(*id), + Self::Synthetic { + kind: SyntheticTypeKind::Qualified, + inner, + } => inner.layout_dwarf_id(), + Self::Synthetic { .. } | Self::Unknown => None, + } + } + + /// Return the underlying DWARF DIE through any script-created wrappers. + pub fn underlying_dwarf_id(&self) -> Option { + match self { + Self::Dwarf(id) => Some(*id), + Self::Synthetic { inner, .. } => inner.underlying_dwarf_id(), + Self::Unknown => None, + } + } + + /// Resolve a projection that consumes only a structural synthetic wrapper. + /// `None` means the projection needs DWARF or is invalid for this wrapper. + pub(crate) fn project_structural(&self, segment: &VariableAccessSegment) -> Option { + match self { + Self::Dwarf(_) => None, + Self::Synthetic { + kind: SyntheticTypeKind::Qualified, + inner, + } => inner.project_structural(segment), + Self::Synthetic { + kind: SyntheticTypeKind::Pointer, + inner, + } => match segment { + VariableAccessSegment::Dereference | VariableAccessSegment::ArrayIndex(_) => { + Some(inner.as_ref().clone()) + } + VariableAccessSegment::Field(_) | VariableAccessSegment::TupleIndex(_) => None, + }, + Self::Synthetic { + kind: SyntheticTypeKind::Array, + inner, + } => match segment { + VariableAccessSegment::ArrayIndex(_) => Some(inner.as_ref().clone()), + _ => None, + }, + Self::Unknown => match segment { + VariableAccessSegment::Dereference | VariableAccessSegment::ArrayIndex(_) => { + Some(Self::Unknown) + } + VariableAccessSegment::Field(_) | VariableAccessSegment::TupleIndex(_) => None, + }, + } + } +} + +impl Default for TypeIdentity { + fn default() -> Self { + Self::Unknown + } +} + +/// Operational type state carried across planning and compiler projections. +/// +/// The summary supplies protocol-compatible physical layout, while identity +/// retains the DWARF DIE beneath any script-created wrappers. +#[derive(Debug, Clone, PartialEq)] +pub struct ResolvedType { + pub summary: TypeInfo, + pub identity: TypeIdentity, + pub origin: Option, +} + +impl ResolvedType { + pub fn new(summary: TypeInfo, identity: TypeIdentity, origin: Option) -> Self { + Self { + summary, + identity, + origin, + } + } + + pub fn synthetic(summary: TypeInfo) -> Self { + Self::new(summary, TypeIdentity::Unknown, None) + } + + pub fn from_semantic_type(semantic: SemanticType) -> Self { + Self::new( + semantic.summary, + TypeIdentity::from_dwarf(semantic.id), + semantic.origin, + ) + } + + /// Project through a script-created pointer or array wrapper. Exact DWARF + /// identities are delegated to the analyzer. + pub fn project_structural(&self, segment: &VariableAccessSegment) -> Option { + let (layout, summary) = match segment { + VariableAccessSegment::Dereference => match strip_type_aliases(&self.summary) { + TypeInfo::PointerType { target_type, .. } => ( + TypeProjectionLayout::Dereference, + target_type.as_ref().clone(), + ), + _ => return None, + }, + VariableAccessSegment::ArrayIndex(_) => { + let element = indexable_element_layout(&self.summary)?; + ( + TypeProjectionLayout::Element { + stride: element.stride, + }, + element.element_type, + ) + } + VariableAccessSegment::Field(_) | VariableAccessSegment::TupleIndex(_) => return None, + }; + let identity = self.identity.project_structural(segment)?; + + Some(TypeProjection { + layout, + resolved_type: Self::new(summary, identity, self.origin.clone()), + }) + } + + pub(crate) fn wrap( + self, + kind: SyntheticTypeKind, + summary: impl FnOnce(TypeInfo) -> TypeInfo, + ) -> Self { + Self { + summary: summary(self.summary), + identity: TypeIdentity::synthetic(kind, self.identity), + origin: self.origin, + } + } +} + +/// Physical address operation and semantic type produced by one projection. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum TypeProjectionLayout { + Dereference, + Member { offset: u64 }, + Element { stride: u64 }, +} + +/// Atomic result of projecting a resolved type through one access segment. +#[derive(Debug, Clone, PartialEq)] +pub struct TypeProjection { + pub layout: TypeProjectionLayout, + pub resolved_type: ResolvedType, +} + #[cfg(test)] mod tests { - use super::SourceLanguage; + use super::{ + ResolvedType, SourceLanguage, SyntheticTypeKind, TypeIdentity, TypeProjectionLayout, + }; + use crate::{CuId, DieRef, ModuleId, TypeId, TypeInfo, VariableAccessSegment}; + + fn type_id() -> TypeId { + TypeId { + module: ModuleId(1), + cu: CuId(2), + die: DieRef { + module: ModuleId(1), + cu: CuId(2), + offset: 3, + }, + } + } #[test] fn normalizes_supported_language_families() { @@ -129,4 +329,79 @@ mod tests { ); assert_eq!(SourceLanguage::from_dwarf(None), SourceLanguage::Unknown); } + + #[test] + fn layout_identity_looks_through_qualifiers_only() { + let dwarf = TypeIdentity::Dwarf(type_id()); + let qualified = TypeIdentity::synthetic(SyntheticTypeKind::Qualified, dwarf.clone()); + let pointer = TypeIdentity::synthetic(SyntheticTypeKind::Pointer, qualified.clone()); + + assert_eq!(qualified.layout_dwarf_id(), Some(type_id())); + assert_eq!(pointer.layout_dwarf_id(), None); + assert_eq!(dwarf.layout_dwarf_id(), Some(type_id())); + } + + #[test] + fn synthetic_projection_does_not_implicitly_consume_pointers_for_members() { + let pointer = TypeIdentity::synthetic( + SyntheticTypeKind::Pointer, + TypeIdentity::synthetic(SyntheticTypeKind::Pointer, TypeIdentity::Dwarf(type_id())), + ); + + assert_eq!( + pointer.project_structural(&VariableAccessSegment::Field("value".to_string())), + None + ); + } + + #[test] + fn structural_projection_keeps_summary_and_identity_in_lockstep() { + let pair = TypeInfo::StructType { + name: "Pair".to_string(), + size: 8, + members: vec![], + }; + let resolved = ResolvedType::new( + TypeInfo::ArrayType { + element_type: Box::new(TypeInfo::PointerType { + target_type: Box::new(pair.clone()), + size: 8, + }), + element_count: Some(2), + total_size: Some(16), + }, + TypeIdentity::synthetic( + SyntheticTypeKind::Array, + TypeIdentity::synthetic(SyntheticTypeKind::Pointer, TypeIdentity::Dwarf(type_id())), + ), + None, + ); + + let pointer = resolved + .project_structural(&VariableAccessSegment::ArrayIndex(0)) + .expect("array element projection"); + assert_eq!(pointer.layout, TypeProjectionLayout::Element { stride: 8 }); + assert!(matches!( + pointer.resolved_type.summary, + TypeInfo::PointerType { .. } + )); + assert!(matches!( + pointer.resolved_type.identity, + TypeIdentity::Synthetic { + kind: SyntheticTypeKind::Pointer, + .. + } + )); + + let pair_projection = pointer + .resolved_type + .project_structural(&VariableAccessSegment::Dereference) + .expect("pointer target projection"); + assert_eq!(pair_projection.layout, TypeProjectionLayout::Dereference); + assert_eq!(pair_projection.resolved_type.summary, pair); + assert_eq!( + pair_projection.resolved_type.identity, + TypeIdentity::Dwarf(type_id()) + ); + } } diff --git a/ghostscope-dwarf/src/semantics/type_layout.rs b/ghostscope-dwarf/src/semantics/type_layout.rs new file mode 100644 index 00000000..c47f2f13 --- /dev/null +++ b/ghostscope-dwarf/src/semantics/type_layout.rs @@ -0,0 +1,181 @@ +//! Language-neutral physical layout operations over normalized DWARF types. + +use crate::TypeInfo; + +#[derive(Clone, Debug, PartialEq)] +pub struct MemberLayout { + pub offset: u64, + pub member_type: TypeInfo, +} + +#[derive(Clone, Debug, PartialEq)] +pub struct IndexableElementLayout { + pub element_type: TypeInfo, + pub stride: u64, +} + +#[derive(Debug, thiserror::Error)] +pub enum TypeLayoutError { + #[error("Unknown member '{field}' in {kind} '{type_name}' (known members: {members})")] + UnknownMember { + kind: &'static str, + type_name: String, + field: String, + members: String, + }, + + #[error("member access requires struct or union type, got '{type_name}'")] + InvalidMemberBase { type_name: String }, +} + +pub fn strip_type_aliases(mut ty: &TypeInfo) -> &TypeInfo { + while let TypeInfo::TypedefType { + underlying_type, .. + } + | TypeInfo::QualifiedType { + underlying_type, .. + } = ty + { + ty = underlying_type.as_ref(); + } + ty +} + +pub fn is_aggregate_type(ty: &TypeInfo) -> bool { + matches!( + strip_type_aliases(ty), + TypeInfo::StructType { .. } | TypeInfo::UnionType { .. } | TypeInfo::ArrayType { .. } + ) +} + +pub fn is_pointer_or_array_type(ty: &TypeInfo) -> bool { + matches!( + strip_type_aliases(ty), + TypeInfo::PointerType { .. } | TypeInfo::ArrayType { .. } + ) +} + +pub fn member_layout(ty: &TypeInfo, field: &str) -> Result { + match strip_type_aliases(ty) { + TypeInfo::StructType { name, members, .. } => members + .iter() + .find(|member| member.name == field) + .map(|member| MemberLayout { + offset: member.offset, + member_type: member.member_type.clone(), + }) + .ok_or_else(|| unknown_member_error("struct", name, field, members)), + TypeInfo::UnionType { name, members, .. } => members + .iter() + .find(|member| member.name == field) + .map(|member| MemberLayout { + offset: member.offset, + member_type: member.member_type.clone(), + }) + .ok_or_else(|| unknown_member_error("union", name, field, members)), + other => Err(TypeLayoutError::InvalidMemberBase { + type_name: other.type_name(), + }), + } +} + +pub fn indexable_element_layout(ty: &TypeInfo) -> Option { + match strip_type_aliases(ty) { + TypeInfo::ArrayType { element_type, .. } => Some(IndexableElementLayout { + element_type: element_type.as_ref().clone(), + stride: element_type.size().max(1), + }), + TypeInfo::PointerType { target_type, .. } => Some(IndexableElementLayout { + element_type: target_type.as_ref().clone(), + stride: target_type.size().max(1), + }), + _ => None, + } +} + +fn unknown_member_error( + kind: &'static str, + type_name: &str, + field: &str, + members: &[crate::StructMember], +) -> TypeLayoutError { + let mut member_names = members + .iter() + .map(|member| member.name.clone()) + .collect::>(); + member_names.sort(); + member_names.dedup(); + let list = if member_names.is_empty() { + "".to_string() + } else { + member_names.join(", ") + }; + + TypeLayoutError::UnknownMember { + kind, + type_name: type_name.to_string(), + field: field.to_string(), + members: list, + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::StructMember; + + fn signed_int() -> TypeInfo { + TypeInfo::BaseType { + name: "int".to_string(), + size: 4, + encoding: crate::constants::DW_ATE_signed.0 as u16, + } + } + + #[test] + fn aggregate_and_index_classification_strip_aliases() { + let array_type = TypeInfo::ArrayType { + element_type: Box::new(signed_int()), + element_count: Some(4), + total_size: Some(16), + }; + let typedef = TypeInfo::TypedefType { + name: "int_array_t".to_string(), + underlying_type: Box::new(array_type), + }; + + assert!(is_aggregate_type(&typedef)); + assert!(is_pointer_or_array_type(&typedef)); + } + + #[test] + fn member_and_index_layout_strip_aliases() { + let signed_int = signed_int(); + let struct_type = TypeInfo::StructType { + name: "Request".to_string(), + size: 16, + members: vec![StructMember { + name: "fd".to_string(), + member_type: signed_int.clone(), + offset: 8, + bit_offset: None, + bit_size: None, + }], + }; + let qualified_struct = TypeInfo::QualifiedType { + qualifier: crate::TypeQualifier::Const, + underlying_type: Box::new(struct_type), + }; + + let layout = member_layout(&qualified_struct, "fd").expect("member layout"); + assert_eq!(layout.offset, 8); + assert_eq!(layout.member_type, signed_int.clone()); + + let pointer_type = TypeInfo::PointerType { + target_type: Box::new(signed_int), + size: 8, + }; + let element = indexable_element_layout(&pointer_type).expect("pointer element layout"); + assert_eq!(element.stride, 4); + } +} diff --git a/ghostscope-dwarf/src/semantics/types.rs b/ghostscope-dwarf/src/semantics/types.rs index f0741f19..920523b6 100644 --- a/ghostscope-dwarf/src/semantics/types.rs +++ b/ghostscope-dwarf/src/semantics/types.rs @@ -7,6 +7,21 @@ pub(crate) struct TypeLoc { pub die_off: gimli::UnitOffset, } +impl TypeLoc { + pub(crate) fn type_id(self, module: crate::ModuleId) -> crate::TypeId { + let cu = crate::CuId(self.cu_off.0 as u32); + crate::TypeId { + module, + cu, + die: crate::DieRef { + module, + cu, + offset: self.die_off.0 as u64, + }, + } + } +} + fn resolve_debug_info_ref( dwarf: &gimli::Dwarf, debug_info_off: gimli::DebugInfoOffset, diff --git a/ghostscope-dwarf/src/semantics/variable_plan/mod.rs b/ghostscope-dwarf/src/semantics/variable_plan/mod.rs index 6aa1f94e..9b01083f 100644 --- a/ghostscope-dwarf/src/semantics/variable_plan/mod.rs +++ b/ghostscope-dwarf/src/semantics/variable_plan/mod.rs @@ -179,6 +179,7 @@ pub struct VariableMaterializationPlan { pub access_path: VariableAccessPath, pub module_path: Option, pub dwarf_type: Option, + pub type_id: Option, pub availability: Availability, pub lowering: VariableLoweringPlan, pub materialization: VariableMaterialization, @@ -457,6 +458,7 @@ impl VariableReadPlan { access_path: self.access_path.clone(), module_path: self.module_path.clone(), dwarf_type: self.dwarf_type.clone(), + type_id: self.type_id, availability: lowering.availability.clone(), lowering, materialization, diff --git a/ghostscope-dwarf/src/semantics/variable_plan/tests.rs b/ghostscope-dwarf/src/semantics/variable_plan/tests.rs index 875887f9..5877b514 100644 --- a/ghostscope-dwarf/src/semantics/variable_plan/tests.rs +++ b/ghostscope-dwarf/src/semantics/variable_plan/tests.rs @@ -1,6 +1,6 @@ use super::*; use crate::core::{AddressExpr, EntryValueCase, MemoryAccessSize, TargetArch}; -use crate::StructMember; +use crate::{CuId, DieRef, ModuleId, StructMember, TypeId}; fn capabilities(regular_uprobe: bool) -> RuntimeCapabilities { RuntimeCapabilities { @@ -211,6 +211,27 @@ fn materialization_plan_preserves_module_path_origin() { ); } +#[test] +fn materialization_plan_preserves_type_identity() { + let type_id = TypeId { + module: ModuleId(3), + cu: CuId(5), + die: DieRef { + module: ModuleId(3), + cu: CuId(5), + offset: 0x42, + }, + }; + let plan = VariableReadPlan { + type_id: Some(type_id), + ..read_plan(VariableLocation::Address(AddressExpr::constant(0x1000))) + }; + + let materialized = plan.materialization_plan(&capabilities(true)); + + assert_eq!(materialized.type_id, Some(type_id)); +} + #[test] fn materialization_plan_converts_register_address_to_address_kind() { let plan = read_plan(VariableLocation::RegisterAddress { diff --git a/ghostscope-dwarf/src/type_syntax/c_style.rs b/ghostscope-dwarf/src/type_syntax/c_style.rs new file mode 100644 index 00000000..09670930 --- /dev/null +++ b/ghostscope-dwarf/src/type_syntax/c_style.rs @@ -0,0 +1,94 @@ +use crate::TypeQualifier; + +/// Parsed form of the C-style type syntax accepted by GhostScope casts. +#[derive(Debug, Clone, PartialEq)] +pub(crate) struct TypeSpec { + pub(crate) base: String, + pub(crate) qualifiers: Vec, + pub(crate) pointer_count: usize, + pub(crate) arrays: Vec>, +} + +pub(crate) fn parse(type_spec: &str) -> Option { + let mut spec = type_spec.trim(); + if spec.is_empty() { + return None; + } + + let mut arrays = Vec::new(); + while let Some((base, count)) = take_array_suffix(spec) { + arrays.push(count); + spec = base.trim_end(); + } + + let mut pointer_count = 0usize; + while let Some(base) = spec.strip_suffix('*') { + pointer_count += 1; + spec = base.trim_end(); + } + + let (qualifiers, base) = strip_leading_qualifiers(spec); + let base = ["struct ", "class ", "union ", "enum "] + .into_iter() + .find_map(|prefix| base.strip_prefix(prefix)) + .unwrap_or(base) + .trim(); + if base.is_empty() { + return None; + } + + Some(TypeSpec { + base: base.to_string(), + qualifiers, + pointer_count, + arrays, + }) +} + +fn take_array_suffix(spec: &str) -> Option<(&str, Option)> { + let spec = spec.trim_end(); + if !spec.ends_with(']') { + return None; + } + let open = spec.rfind('[')?; + let inside = spec[open + 1..spec.len() - 1].trim(); + let count = if inside.is_empty() { + None + } else { + Some(inside.parse::().ok()?) + }; + Some((&spec[..open], count)) +} + +fn strip_leading_qualifiers(mut spec: &str) -> (Vec, &str) { + let mut qualifiers = Vec::new(); + loop { + let trimmed = spec.trim_start(); + if let Some(rest) = trimmed.strip_prefix("const ") { + qualifiers.push(TypeQualifier::Const); + spec = rest; + } else if let Some(rest) = trimmed.strip_prefix("volatile ") { + qualifiers.push(TypeQualifier::Volatile); + spec = rest; + } else if let Some(rest) = trimmed.strip_prefix("restrict ") { + qualifiers.push(TypeQualifier::Restrict); + spec = rest; + } else { + return (qualifiers, trimmed); + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn parses_qualified_pointer_array() { + let spec = parse("const struct Pair *[4]").expect("type spec"); + assert_eq!(spec.base, "Pair"); + assert_eq!(spec.qualifiers, vec![TypeQualifier::Const]); + assert_eq!(spec.pointer_count, 1); + assert_eq!(spec.arrays, vec![Some(4)]); + } +} diff --git a/ghostscope-dwarf/src/type_syntax/mod.rs b/ghostscope-dwarf/src/type_syntax/mod.rs new file mode 100644 index 00000000..39248850 --- /dev/null +++ b/ghostscope-dwarf/src/type_syntax/mod.rs @@ -0,0 +1,3 @@ +//! Parsers for the explicit type syntaxes accepted by GhostScope APIs. + +pub(crate) mod c_style;