Skip to main content

miden_mast_package/package/
mod.rs

1#[cfg(any(test, feature = "arbitrary"))]
2pub mod arbitrary;
3mod error;
4mod id;
5mod manifest;
6mod section;
7#[cfg(test)]
8mod seed_gen;
9mod serialization;
10mod target_type;
11
12use alloc::{
13    borrow::Cow,
14    boxed::Box,
15    collections::BTreeMap,
16    format,
17    string::{String, ToString},
18    sync::Arc,
19    vec::Vec,
20};
21
22use miden_assembly_syntax::{
23    Path, Report,
24    ast::{self, QualifiedProcedureName},
25    module::ModuleInfo,
26};
27#[cfg(feature = "std")]
28use miden_core::serde::DeserializationError;
29use miden_core::{
30    Word,
31    advice::AdviceMap,
32    crypto::hash::Poseidon2,
33    mast::{MastForest, MastNode, MastNodeExt, MastNodeId},
34    program::Kernel,
35    serde::{ByteReader, ByteWriter, Deserializable, Serializable, SliceReader},
36};
37
38pub use self::{
39    error::{PackageDebugInfoError, PackageStripError},
40    id::PackageId,
41    manifest::{
42        ConstantExport, ManifestValidationError, PackageExport, PackageManifest, PackageModule,
43        PackageSubmodule, ProcedureExport, TypeExport,
44    },
45    section::{InvalidSectionIdError, Section, SectionId},
46    target_type::{InvalidTargetTypeError, TargetType},
47};
48use crate::{
49    Dependency, Version,
50    debug_info::{
51        DebugFunctionIdx, DebugFunctionInfo, DebugSourceNode, DebugSourceNodeId, DebugStringIdx,
52        DebugTypeIdx, DebugTypeInfo, PackageDebugInfo,
53    },
54};
55
56// PACKAGE
57// ================================================================================================
58
59/// A package is a assembled artifact containing:
60///
61/// * Basic metadata like name, description, and semantic version
62/// * The type of target the package represents, e.g. a library or executable
63/// * A manifest describing the contents of the package, see [PackageManifest] for more details.
64/// * A [MastForest] corresponding to the assembled target
65/// * One or more custom sections containing metadata produced by the assembler or other tools which
66///   is relevant to the package, e.g. debug symbols.
67///
68/// Custom sections which are of particular interest:
69///
70/// * For account components, the package will contain a section that provides component metadata
71/// * For executable packages which link against a kernel, the package will embed the kernel package
72///   in a custom section, so that executables are "self-contained".
73/// * When assembled with debug information, various types of debug info are emitted to custom
74///   sections for use by debuggers and other introspection tooling.
75///
76/// See [SectionId] for the set of well-known sections, and what they are used for.
77#[derive(Debug, Clone, Eq, PartialEq)]
78pub struct Package {
79    /// Name of the package
80    pub name: PackageId,
81    /// An optional semantic version for the package
82    pub version: Version,
83    /// The content hash of the exported code of this package, formed by hashing the roots of all
84    /// exports in lexicographical order (by digest, not procedure name)
85    digest: Word,
86    /// An optional description of the package
87    pub description: Option<String>,
88    /// The project target type which produced this package
89    pub kind: TargetType,
90    /// The underlying [MastForest] of this package
91    mast: Arc<MastForest>,
92    /// The package manifest, containing the set of exported procedures and their signatures,
93    /// if known.
94    pub manifest: PackageManifest,
95    /// The set of custom sections included with the package, e.g. debug information, account
96    /// metadata, etc.
97    pub sections: Vec<Section>,
98    /// Whether package-owned debug sections may be decoded as trusted debug info.
99    ///
100    /// Normal package deserialization validates the embedded MAST forest, warns on package debug
101    /// sections, and discards those sections as untrusted metadata. Trusted local/cache readers
102    /// and in-process package construction preserve package debug sections and expose them through
103    /// [`Package::debug_info`].
104    debug_sections_trusted: bool,
105}
106
107/// Construction
108impl Package {
109    /// Construct a [Package] from its essential component parts
110    pub fn create(
111        name: PackageId,
112        version: Version,
113        kind: TargetType,
114        mast: Arc<MastForest>,
115        exports: impl IntoIterator<Item = PackageExport>,
116        dependencies: impl IntoIterator<Item = Dependency>,
117    ) -> Result<Self, ManifestValidationError> {
118        Self::create_with_modules(name, version, kind, mast, exports, [], dependencies)
119    }
120
121    /// Construct a [Package] from its essential component parts and module surface metadata.
122    pub fn create_with_modules(
123        name: PackageId,
124        version: Version,
125        kind: TargetType,
126        mast: Arc<MastForest>,
127        exports: impl IntoIterator<Item = PackageExport>,
128        modules: impl IntoIterator<Item = PackageModule>,
129        dependencies: impl IntoIterator<Item = Dependency>,
130    ) -> Result<Self, ManifestValidationError> {
131        let manifest = PackageManifest::new(exports)?
132            .with_modules(modules)?
133            .with_dependencies(dependencies)?;
134
135        if manifest.entrypoint().is_some() && !kind.is_executable() {
136            return Err(ManifestValidationError::NonExecutableEntrypoint);
137        }
138
139        // Validate that procedure export node provenance is valid when present
140        for export in manifest.exports() {
141            if let Some(proc) = export.as_procedure()
142                && let Some(node) = proc.node
143                && !mast.is_procedure_root_with_exact_digest(node, proc.digest)
144            {
145                return Err(ManifestValidationError::InvalidProcedureExport {
146                    path: proc.path.clone(),
147                });
148            }
149        }
150
151        let mut package = Self {
152            name,
153            version,
154            digest: Default::default(),
155            description: None,
156            kind,
157            mast,
158            manifest,
159            sections: Vec::new(),
160            debug_sections_trusted: true,
161        };
162
163        package.compute_interface_digest()?;
164        package.recompute_mast_commitment();
165
166        Ok(package)
167    }
168
169    fn compute_interface_digest(&self) -> Result<Word, ManifestValidationError> {
170        let mut node_ids = Vec::with_capacity(self.manifest.num_exports());
171        for export in self.manifest.exports() {
172            if let PackageExport::Procedure(export) = export {
173                if let Some(node_id) = export.node {
174                    node_ids.push(node_id);
175                } else {
176                    node_ids.push(self.mast.find_procedure_root(export.digest).ok_or_else(
177                        || ManifestValidationError::MissingProcedureMast {
178                            path: export.path.clone(),
179                            digest: export.digest,
180                        },
181                    )?);
182                }
183            }
184        }
185
186        Ok(self.mast.compute_nodes_commitment(node_ids.iter()))
187    }
188
189    fn recompute_mast_commitment(&mut self) {
190        self.digest = self.mast.commitment();
191    }
192
193    /// Produces a new library with the existing [`MastForest`] and where all key/values in the
194    /// provided advice map are added to the internal advice map.
195    pub fn with_advice_map(mut self, advice_map: AdviceMap) -> Self {
196        self.extend_advice_map(advice_map);
197        self
198    }
199
200    /// Extends the advice map of this library
201    pub fn extend_advice_map(&mut self, advice_map: AdviceMap) {
202        self.mast = Arc::new(self.mast.as_ref().clone().with_advice_map(advice_map));
203        self.recompute_mast_commitment();
204    }
205
206    /// Removes all package-owned debug information from this package.
207    ///
208    /// This removes well-known package debug sections and recursively strips an embedded kernel
209    /// package if one is present.
210    pub fn strip_debug_info(&mut self) -> Result<(), PackageStripError> {
211        for section in self.sections.iter_mut().filter(|section| section.id == SectionId::KERNEL) {
212            let mut kernel_package = Self::read_from_bytes(section.data.as_ref())
213                .map_err(|source| PackageStripError::DecodeEmbeddedKernel { source })?;
214            kernel_package.strip_debug_info()?;
215            section.data = Cow::Owned(kernel_package.to_bytes());
216        }
217
218        self.sections.retain(|section| !section.id.is_debug());
219        Ok(())
220    }
221
222    /// Returns this package with package-owned debug information removed.
223    pub fn without_debug_info(mut self) -> Result<Self, PackageStripError> {
224        self.strip_debug_info()?;
225        Ok(self)
226    }
227}
228
229/// Accessors
230impl Package {
231    /// The file extension given to serialized packages
232    pub const EXTENSION: &str = "masp";
233
234    /// Returns a reference to the MAST contained in this package
235    #[inline]
236    pub fn mast_forest(&self) -> &Arc<MastForest> {
237        &self.mast
238    }
239
240    /// Returns the digest of the package's MAST artifact
241    #[inline]
242    pub fn digest(&self) -> Word {
243        self.digest
244    }
245
246    /// Returns the digest of the exported procedure roots used by the linker.
247    pub fn interface_digest(&self) -> Result<Word, ManifestValidationError> {
248        self.compute_interface_digest()
249    }
250
251    /// Returns a digest of the package content relevant to assembly and dependency resolution.
252    ///
253    /// This is distinct from [`Self::digest`], which is only the digest of the underlying MAST
254    /// artifact. The content digest currently binds the MAST digest, package name, semantic
255    /// version, package kind, manifest, and any semantic package sections. Package descriptions
256    /// and opaque custom sections are intentionally excluded for now; kernel-section binding is
257    /// added separately.
258    pub fn content_digest(&self) -> Word {
259        let mut bytes = Vec::new();
260        self.write_content_digest_preimage(&mut bytes, None);
261        Poseidon2::hash(&bytes)
262    }
263
264    fn write_content_digest_preimage<W: ByteWriter>(
265        &self,
266        target: &mut W,
267        kernel_digest: Option<&Word>,
268    ) {
269        target.write_bytes(b"miden.package.content.v2");
270        self.digest().write_into(target);
271        self.name.write_into(target);
272        self.version.to_string().write_into(target);
273        target.write_u8(self.kind.into());
274        self.manifest.write_into(target);
275        self.write_content_digest_sections(target);
276        target.write_bool(kernel_digest.is_some());
277        if let Some(kernel_digest) = kernel_digest {
278            kernel_digest.write_into(target);
279        }
280    }
281
282    fn write_content_digest_sections<W: ByteWriter>(&self, target: &mut W) {
283        let semantic_sections = self
284            .sections
285            .iter()
286            .filter(|section| section.id == SectionId::ACCOUNT_COMPONENT_METADATA)
287            .collect::<Vec<_>>();
288        target.write_usize(semantic_sections.len());
289        for section in semantic_sections {
290            section.write_into(target);
291        }
292    }
293
294    /// Returns true if this package was produced for an executable target
295    pub fn is_program(&self) -> bool {
296        self.kind.is_executable()
297    }
298
299    /// Returns true if this package was produced for a library or kernel target
300    pub fn is_library(&self) -> bool {
301        self.kind.is_library()
302    }
303
304    /// Returns true if this package was produced specifically for a kernel target
305    pub fn is_kernel(&self) -> bool {
306        matches!(self.kind, TargetType::Kernel)
307    }
308
309    /// Returns the absolute path of the entrypoint procedure for this package, if it is executable
310    #[inline]
311    pub fn entrypoint(&self) -> Option<Arc<Path>> {
312        self.manifest.entrypoint()
313    }
314
315    /// Returns the source/debug occurrence for the executable entrypoint, if recorded.
316    #[inline]
317    pub fn entrypoint_source_node(&self) -> Option<DebugSourceNodeId> {
318        self.entrypoint()
319            .as_deref()
320            .and_then(|entrypoint| self.get_export_by_lookup_path(entrypoint))
321            .and_then(PackageExport::as_procedure)
322            .and_then(|procedure| procedure.source_node)
323    }
324
325    /// Get the [ModuleInfo] corresponding to the kernel module, if this package contains the kernel
326    pub fn kernel_module_info(&self) -> Result<ModuleInfo, Report> {
327        self.try_module_infos()
328            .map_err(Report::msg)?
329            .into_iter()
330            .find(|mi| mi.path().is_kernel_path())
331            .ok_or_else(|| Report::msg("invalid kernel package: does not contain kernel module"))
332    }
333
334    /// If this package depends on a kernel, this method extracts the [Dependency] corresponding to
335    /// it.
336    ///
337    /// Returns `Err` if the dependency metadata for this package contains multiple kernels.
338    pub fn kernel_runtime_dependency(&self) -> Result<Option<&Dependency>, Report> {
339        let mut kernel_dependencies = self
340            .manifest
341            .dependencies()
342            .filter(|dependency| dependency.kind == TargetType::Kernel);
343        let Some(kernel_dependency) = kernel_dependencies.next() else {
344            return Ok(None);
345        };
346        if kernel_dependencies.next().is_some() {
347            return Err(Report::msg(format!(
348                "package '{}' declares multiple kernel runtime dependencies",
349                self.name
350            )));
351        }
352
353        Ok(Some(kernel_dependency))
354    }
355
356    /// Decodes trusted package-owned debug sections, if any are present.
357    ///
358    /// Package debug sections are trusted only for packages constructed in-process or read via the
359    /// trusted same-domain readers such as [`Self::read_from_trusted`],
360    /// [`Self::read_from_bytes_trusted`], [`Self::read_from_unchecked`], and
361    /// [`Self::read_from_bytes_unchecked`]. Normal untrusted readers discard debug sections before
362    /// returning the package.
363    ///
364    /// This does not read legacy debug metadata from the embedded [`MastForest`].
365    pub fn debug_info(&self) -> Result<Option<PackageDebugInfo>, PackageDebugInfoError> {
366        if !self.debug_sections_trusted && self.sections.iter().any(|section| section.id.is_debug())
367        {
368            return Err(PackageDebugInfoError::UntrustedSections);
369        }
370
371        let debug_info = self.read_debug_section::<PackageDebugInfo>(SectionId::DEBUG_INFO)?;
372
373        if let Some(debug_info) = debug_info.as_ref() {
374            self.validate_debug_info(debug_info)?;
375        }
376
377        Ok(debug_info)
378    }
379
380    /// Returns a MAST node ID associated with the specified exported procedure.
381    ///
382    /// # Panics
383    ///
384    /// Panics if the specified procedure is not exported from this package.
385    pub fn get_export_node_id(&self, path: impl AsRef<Path>) -> MastNodeId {
386        self.get_export_by_lookup_path(path.as_ref())
387            .and_then(PackageExport::as_procedure)
388            .and_then(|export| export.node.or_else(|| self.mast.find_procedure_root(export.digest)))
389            .expect("procedure not exported from this package")
390    }
391
392    /// Returns true if the specified exported procedure is re-exported from a dependency.
393    pub fn is_reexport(&self, path: impl AsRef<Path>) -> bool {
394        self.get_export_by_lookup_path(path.as_ref())
395            .and_then(PackageExport::as_procedure)
396            .and_then(|export| export.node.or_else(|| self.mast.find_procedure_root(export.digest)))
397            .map(|node| self.mast[node].is_external())
398            .unwrap_or(false)
399    }
400
401    /// Returns the digest of the procedure with the specified name, or `None` if it was not found
402    /// in the library or its library path is malformed.
403    pub fn get_procedure_root_by_path(&self, path: impl AsRef<Path>) -> Option<Word> {
404        self.get_export_by_lookup_path(path.as_ref())
405            .and_then(PackageExport::as_procedure)
406            .map(|proc| proc.digest)
407    }
408
409    /// Returns the exact procedure node for the specified path, if it is present.
410    pub fn get_procedure_node_by_path(&self, path: impl AsRef<Path>) -> Option<MastNodeId> {
411        self.get_export_by_lookup_path(path.as_ref())
412            .and_then(PackageExport::as_procedure)
413            .and_then(|export| export.node.or_else(|| self.mast.find_procedure_root(export.digest)))
414    }
415
416    fn get_export_by_lookup_path(&self, path: &Path) -> Option<&PackageExport> {
417        self.manifest
418            .get_export(path)
419            .or_else(|| path.is_absolute().then(|| self.manifest.get_export(path.to_relative()))?)
420            .or_else(|| {
421                if path.is_absolute() {
422                    None
423                } else {
424                    path.to_absolute().ok().and_then(|path| self.manifest.get_export(path.as_ref()))
425                }
426            })
427    }
428
429    /// Returns an iterator over the module infos of the library.
430    pub fn module_infos(&self) -> impl Iterator<Item = ModuleInfo> {
431        let source_library_commitment =
432            self.interface_digest().expect("package manifest exports were validated");
433        let mut modules_by_path: BTreeMap<Arc<Path>, ModuleInfo> = BTreeMap::new();
434
435        for module in self.manifest.modules() {
436            let mut module_info = ModuleInfo::new(module.path.clone(), None);
437            for submodule in module.submodules() {
438                module_info.add_submodule(ast::SubmoduleDecl {
439                    visibility: ast::Visibility::Public,
440                    name: submodule.name.clone(),
441                });
442            }
443            modules_by_path.insert(module.path.clone(), module_info);
444        }
445
446        for export in self.manifest.exports() {
447            let module_name =
448                Arc::from(export.path().parent().unwrap().to_path_buf().into_boxed_path());
449            let module = modules_by_path
450                .entry(Arc::clone(&module_name))
451                .or_insert_with(|| ModuleInfo::new(module_name, None));
452            match export {
453                PackageExport::Procedure(ProcedureExport {
454                    node,
455                    source_node,
456                    digest,
457                    path,
458                    signature,
459                    attributes,
460                }) => {
461                    let name = path.procedure_name().expect("valid procedure name").unwrap();
462                    module.add_procedure_with_provenance(
463                        name,
464                        *digest,
465                        signature.clone().map(Arc::new),
466                        attributes.clone(),
467                        *node,
468                        source_node.map(u32::from),
469                        Some(source_library_commitment),
470                    );
471                },
472                PackageExport::Constant(ConstantExport { path, value }) => {
473                    let name =
474                        path.components().next_back().unwrap().expect("valid path component");
475                    let name = name.to_ident().expect("valid identifier");
476                    module.add_constant(name, value.clone());
477                },
478                PackageExport::Type(TypeExport { path, ty }) => {
479                    let name =
480                        path.components().next_back().unwrap().expect("valid path component");
481                    let name = name.to_ident().expect("valid identifier");
482                    module.add_type(name, ty.clone());
483                },
484            }
485        }
486
487        modules_by_path.into_values()
488    }
489
490    /// Returns module infos after validating that manifest module-surface metadata is complete.
491    ///
492    /// Unlike [`Self::module_infos`], this method does not synthesize missing module surfaces from
493    /// item export paths. Link-time resolution relies on explicit module metadata so that modules
494    /// remain distinct from exported items.
495    pub fn try_module_infos(&self) -> Result<Vec<ModuleInfo>, ManifestValidationError> {
496        let source_library_commitment = self.interface_digest()?;
497        let mut modules_by_path: BTreeMap<Arc<Path>, ModuleInfo> = BTreeMap::new();
498
499        for module in self.manifest.modules() {
500            let mut module_info = ModuleInfo::new(module.path.clone(), None);
501            for submodule in module.submodules() {
502                module_info.add_submodule(ast::SubmoduleDecl {
503                    visibility: ast::Visibility::Public,
504                    name: submodule.name.clone(),
505                });
506            }
507            modules_by_path.insert(module.path.clone(), module_info);
508        }
509
510        for module in self.manifest.modules() {
511            for submodule in module.submodules() {
512                let child_path: Arc<Path> =
513                    Arc::from(module.path.join(&submodule.name).into_boxed_path());
514                if !modules_by_path.contains_key(child_path.as_ref()) {
515                    return Err(ManifestValidationError::MissingDeclaredSubmoduleSurface {
516                        parent: module.path.clone(),
517                        name: submodule.name.to_string(),
518                        module: child_path,
519                    });
520                }
521            }
522        }
523
524        for module in self.manifest.modules() {
525            let Some(parent_path) = module.path.parent() else {
526                continue;
527            };
528            let parent_path: Arc<Path> = Arc::from(parent_path.to_path_buf().into_boxed_path());
529            let Some(parent) = self.manifest.get_module(parent_path.as_ref()) else {
530                continue;
531            };
532            let name = module.path.last().expect("module paths have at least one component");
533            if !parent.submodules().iter().any(|submodule| submodule.name.as_str() == name) {
534                return Err(ManifestValidationError::UndeclaredModuleSurface {
535                    module: module.path.clone(),
536                    parent: parent.path.clone(),
537                    name: name.to_string(),
538                });
539            }
540        }
541
542        for export in self.manifest.exports() {
543            let module_name: Arc<Path> =
544                Arc::from(export.path().parent().unwrap().to_path_buf().into_boxed_path());
545            let module = modules_by_path.get_mut(module_name.as_ref()).ok_or_else(|| {
546                ManifestValidationError::MissingExportModuleSurface {
547                    export: export.path(),
548                    module: module_name.clone(),
549                }
550            })?;
551            match export {
552                PackageExport::Procedure(ProcedureExport {
553                    node,
554                    source_node,
555                    digest,
556                    path,
557                    signature,
558                    attributes,
559                }) => {
560                    let name = path.procedure_name().expect("valid procedure name").unwrap();
561                    module.add_procedure_with_provenance(
562                        name,
563                        *digest,
564                        signature.clone().map(Arc::new),
565                        attributes.clone(),
566                        *node,
567                        source_node.map(u32::from),
568                        Some(source_library_commitment),
569                    );
570                },
571                PackageExport::Constant(ConstantExport { path, value }) => {
572                    let name =
573                        path.components().next_back().unwrap().expect("valid path component");
574                    let name = name.to_ident().expect("valid identifier");
575                    module.add_constant(name, value.clone());
576                },
577                PackageExport::Type(TypeExport { path, ty }) => {
578                    let name =
579                        path.components().next_back().unwrap().expect("valid path component");
580                    let name = name.to_ident().expect("valid identifier");
581                    module.add_type(name, ty.clone());
582                },
583            }
584        }
585
586        Ok(modules_by_path.into_values().collect())
587    }
588
589    fn read_debug_section<T>(&self, id: SectionId) -> Result<Option<T>, PackageDebugInfoError>
590    where
591        T: Deserializable,
592    {
593        let mut sections = self.sections.iter().filter(|section| section.id == id);
594        let Some(section) = sections.next() else {
595            return Ok(None);
596        };
597        if sections.next().is_some() {
598            return Err(PackageDebugInfoError::DuplicateSection { id });
599        }
600
601        read_section_payload(&id, section.data.as_ref()).map(Some)
602    }
603
604    fn validate_debug_info(
605        &self,
606        debug_info: &PackageDebugInfo,
607    ) -> Result<(), PackageDebugInfoError> {
608        self.validate_debug_sources(debug_info)?;
609        self.validate_debug_types(debug_info)?;
610        self.validate_debug_functions(debug_info)?;
611
612        for root in debug_info.roots().iter().copied() {
613            if debug_info.source_node(root).is_none() {
614                return Err(PackageDebugInfoError::InvalidReference {
615                    message: format!("debug source root {root:?} is not present in the graph"),
616                });
617            }
618        }
619
620        for (source_index, source_node) in debug_info.nodes().iter().enumerate() {
621            let source_id = DebugSourceNodeId::from(source_index as u32);
622            let Some(exec_node) = self.mast.get_node_by_id(source_node.exec_node) else {
623                return Err(PackageDebugInfoError::InvalidReference {
624                    message: format!(
625                        "debug source node {source_id:?} references missing execution node {:?}",
626                        source_node.exec_node,
627                    ),
628                });
629            };
630            if source_node.op_start > source_node.op_end {
631                return Err(PackageDebugInfoError::InvalidReference {
632                    message: format!(
633                        "debug source node {source_id:?} has invalid operation range {}..{}",
634                        source_node.op_start, source_node.op_end,
635                    ),
636                });
637            }
638            if let MastNode::Block(block) = exec_node {
639                let num_ops = block.num_operations();
640                if source_node.op_end > num_ops {
641                    return Err(PackageDebugInfoError::InvalidReference {
642                        message: format!(
643                            "debug source node {source_id:?} has operation range {}..{}, outside execution node {:?} operation count {num_ops}",
644                            source_node.op_start, source_node.op_end, source_node.exec_node,
645                        ),
646                    });
647                }
648            }
649
650            let function_count = debug_info.functions().len();
651            let loc_count = debug_info.locations().len();
652            let mut exec_children = Vec::new();
653            exec_node.for_each_child(|child_id| exec_children.push(child_id));
654            if exec_children.len() != source_node.children.len() {
655                return Err(PackageDebugInfoError::InvalidReference {
656                    message: format!(
657                        "debug source node {source_id:?} has {} children, expected {} from execution node {:?}",
658                        source_node.children.len(),
659                        exec_children.len(),
660                        source_node.exec_node,
661                    ),
662                });
663            }
664
665            for (child_index, child_source_id) in source_node.children.iter().copied().enumerate() {
666                let Some(child_source_node) = debug_info.source_node(child_source_id) else {
667                    return Err(PackageDebugInfoError::InvalidReference {
668                        message: format!(
669                            "debug source node {source_id:?} references missing child source node {child_source_id:?}",
670                        ),
671                    });
672                };
673                if child_source_node.exec_node != exec_children[child_index] {
674                    return Err(PackageDebugInfoError::InvalidReference {
675                        message: format!(
676                            "debug source node {source_id:?} child {child_index} maps to {:?}, expected {:?}",
677                            child_source_node.exec_node, exec_children[child_index],
678                        ),
679                    });
680                }
681            }
682            for row in source_node.asm_ops.iter() {
683                self.validate_source_map_row(source_id, source_node, row.op_idx, "assembly op")?;
684                self.validate_string_index(row.context_name_idx, debug_info, || {
685                    format!("debug source node {source_id:?} assembly op context name")
686                })?;
687                self.validate_string_index(row.op_name_idx, debug_info, || {
688                    format!("debug source node {source_id:?} assembly op name")
689                })?;
690                if let Some(location_idx) = row.location_idx.try_into_option().map_err(|err| {
691                    PackageDebugInfoError::InvalidOptionField {
692                        err,
693                        context: format!("debug source node {source_id:?} assembly op location"),
694                    }
695                })? {
696                    self.validate_location_index(location_idx, debug_info, || {
697                        format!("debug source node {source_id:?} assembly op location")
698                    })?;
699                }
700            }
701            for row in source_node.debug_vars.iter() {
702                self.validate_source_map_row(source_id, source_node, row.op_idx, "debug variable")?;
703                self.validate_string_index(row.name_idx, debug_info, || {
704                    format!("debug source node {source_id:?} variable name")
705                })?;
706                if let Some(type_idx) = row.type_id {
707                    self.validate_type_index(type_idx, debug_info, || {
708                        format!("debug source node {source_id:?} variable")
709                    })?;
710                }
711                if let Some(location_idx) = row.location_idx {
712                    self.validate_location_index(location_idx, debug_info, || {
713                        format!("debug source node {source_id:?} variable location")
714                    })?;
715                }
716            }
717
718            for row in source_node.inline_calls.iter() {
719                self.validate_source_map_row(source_id, source_node, row.op_idx, "inline call")?;
720                if debug_info.get_function(row.callee_idx).is_none() {
721                    return Err(PackageDebugInfoError::InvalidReference {
722                        message: format!(
723                            "debug inline call callee index {} is outside debug function table length {function_count}",
724                            row.callee_idx,
725                        ),
726                    });
727                }
728                if debug_info.get_location(row.loc_idx).is_none() {
729                    return Err(PackageDebugInfoError::InvalidReference {
730                        message: format!(
731                            "debug inline call loc index {} is outside debug source location table length {loc_count}",
732                            row.loc_idx,
733                        ),
734                    });
735                }
736            }
737        }
738
739        for export in self.manifest.exports() {
740            let Some(procedure) = export.as_procedure() else {
741                continue;
742            };
743            let Some(source_node_id) = procedure.source_node else {
744                continue;
745            };
746            let Some(source_node) = debug_info.source_node(source_node_id) else {
747                return Err(PackageDebugInfoError::InvalidReference {
748                    message: format!(
749                        "procedure export '{}' references missing source node {source_node_id:?}",
750                        procedure.path,
751                    ),
752                });
753            };
754            let Some(export_node) =
755                procedure.node.or_else(|| self.mast.find_procedure_root(procedure.digest))
756            else {
757                return Err(PackageDebugInfoError::InvalidReference {
758                    message: format!(
759                        "procedure export '{}' does not resolve to an execution node",
760                        procedure.path,
761                    ),
762                });
763            };
764            if source_node.exec_node != export_node {
765                return Err(PackageDebugInfoError::InvalidReference {
766                    message: format!(
767                        "procedure export '{}' source node {source_node_id:?} maps to {:?}, expected {export_node:?}",
768                        procedure.path, source_node.exec_node,
769                    ),
770                });
771            }
772        }
773
774        Ok(())
775    }
776
777    fn validate_debug_types(
778        &self,
779        debug_info: &PackageDebugInfo,
780    ) -> Result<(), PackageDebugInfoError> {
781        for (i, ty) in debug_info.types().iter().enumerate() {
782            let index = DebugTypeIdx::from(i as u32);
783            self.validate_debug_type(index, ty, debug_info)?;
784        }
785        Ok(())
786    }
787
788    fn validate_debug_type(
789        &self,
790        type_index: DebugTypeIdx,
791        ty: &DebugTypeInfo,
792        debug_info: &PackageDebugInfo,
793    ) -> Result<(), PackageDebugInfoError> {
794        match ty {
795            DebugTypeInfo::Primitive(_) | DebugTypeInfo::Unknown => Ok(()),
796            DebugTypeInfo::Pointer { pointee_type_idx } => {
797                self.validate_type_index(*pointee_type_idx, debug_info, || {
798                    format!("debug type {type_index} pointer target")
799                })
800            },
801            DebugTypeInfo::Array { element_type_idx, .. } => {
802                self.validate_type_index(*element_type_idx, debug_info, || {
803                    format!("debug type {type_index} array element")
804                })
805            },
806            DebugTypeInfo::Struct { name_idx, fields, .. } => {
807                self.validate_string_index(*name_idx, debug_info, || {
808                    format!("debug type {type_index} struct name")
809                })?;
810                for (field_index, field) in fields.iter().enumerate() {
811                    self.validate_string_index(field.name_idx, debug_info, || {
812                        format!("debug type {type_index} field {field_index} name")
813                    })?;
814                    self.validate_type_index(field.type_idx, debug_info, || {
815                        format!("debug type {type_index} field {field_index} type")
816                    })?;
817                }
818                Ok(())
819            },
820            DebugTypeInfo::Function { return_type_idx, param_type_indices } => {
821                if let Some(return_type_idx) = return_type_idx {
822                    self.validate_type_index(*return_type_idx, debug_info, || {
823                        format!("debug type {type_index} function return type")
824                    })?;
825                }
826                for (param_index, param_type_idx) in param_type_indices.iter().copied().enumerate()
827                {
828                    self.validate_type_index(param_type_idx, debug_info, || {
829                        format!("debug type {type_index} function parameter {param_index}")
830                    })?;
831                }
832                Ok(())
833            },
834            DebugTypeInfo::Enum {
835                name_idx,
836                discriminant_type_idx,
837                variants,
838                ..
839            } => {
840                self.validate_string_index(*name_idx, debug_info, || {
841                    format!("debug type {type_index} enum name")
842                })?;
843                self.validate_type_index(*discriminant_type_idx, debug_info, || {
844                    format!("debug type {type_index} enum discriminant")
845                })?;
846                for (variant_index, variant) in variants.iter().enumerate() {
847                    self.validate_string_index(variant.name_idx, debug_info, || {
848                        format!("debug type {type_index} variant {variant_index} name")
849                    })?;
850                    if let Some(type_idx) = variant.type_idx {
851                        self.validate_type_index(type_idx, debug_info, || {
852                            format!("debug type {type_index} variant {variant_index} payload")
853                        })?;
854                    }
855                }
856                Ok(())
857            },
858        }
859    }
860
861    fn validate_debug_sources(
862        &self,
863        debug_info: &PackageDebugInfo,
864    ) -> Result<(), PackageDebugInfoError> {
865        for (file_index, file) in debug_info.files().iter().enumerate() {
866            self.validate_string_index(file.path_idx, debug_info, || {
867                format!("debug source file {file_index} path")
868            })?;
869        }
870        for (location_index, location) in debug_info.locations().iter().enumerate() {
871            if debug_info.get_file(location.file_idx).is_none() {
872                return Err(PackageDebugInfoError::InvalidReference {
873                    message: format!(
874                        "debug source location {location_index} file index {} is outside debug source file table length {}",
875                        location.file_idx,
876                        debug_info.files().len(),
877                    ),
878                });
879            }
880        }
881        for (message_index, message) in debug_info.error_messages().iter().enumerate() {
882            self.validate_string_index(message.message, debug_info, || {
883                format!("debug error message {message_index}")
884            })?;
885        }
886        Ok(())
887    }
888
889    fn validate_debug_functions(
890        &self,
891        debug_info: &PackageDebugInfo,
892    ) -> Result<(), PackageDebugInfoError> {
893        for (function_index, function) in debug_info.functions().iter().enumerate() {
894            let function_index = DebugFunctionIdx::from(function_index as u32);
895            self.validate_debug_function(function, function_index, debug_info)?;
896        }
897        Ok(())
898    }
899
900    fn validate_debug_function(
901        &self,
902        function: &DebugFunctionInfo,
903        function_index: DebugFunctionIdx,
904        debug_info: &PackageDebugInfo,
905    ) -> Result<(), PackageDebugInfoError> {
906        self.validate_string_index(function.name_idx, debug_info, || {
907            format!("debug function {function_index} name")
908        })?;
909        if let Some(linkage_name_idx) =
910            function.linkage_name_idx.try_into_option().map_err(|err| {
911                PackageDebugInfoError::InvalidOptionField {
912                    err,
913                    context: format!("debug function {function_index} linkage name"),
914                }
915            })?
916        {
917            self.validate_string_index(linkage_name_idx, debug_info, || {
918                format!("debug function {function_index} linkage name")
919            })?;
920        }
921        if debug_info.get_file(function.file_idx).is_none() {
922            return Err(PackageDebugInfoError::InvalidReference {
923                message: format!(
924                    "debug function {function_index} file index {} is outside debug source file table length {}",
925                    function.file_idx,
926                    debug_info.files().len()
927                ),
928            });
929        }
930        let source_node = function.source_node.try_into_option().map_err(|err| {
931            PackageDebugInfoError::InvalidOptionField {
932                err,
933                context: format!("debug function {function_index} source node"),
934            }
935        })?;
936        if let Some(source_node) = source_node
937            && debug_info.source_node(source_node).is_none()
938        {
939            return Err(PackageDebugInfoError::InvalidReference {
940                message: format!(
941                    "debug function {function_index} source node {source_node:?} is outside debug source node table length {}",
942                    debug_info.nodes().len(),
943                ),
944            });
945        }
946        if let Some(type_idx) = function.type_idx.try_into_option().map_err(|err| {
947            PackageDebugInfoError::InvalidOptionField {
948                err,
949                context: format!("debug function {function_index} type"),
950            }
951        })? {
952            self.validate_type_index(type_idx, debug_info, || {
953                format!("debug function {function_index} type")
954            })?;
955        }
956        Ok(())
957    }
958
959    fn validate_string_index(
960        &self,
961        index: DebugStringIdx,
962        debug_info: &PackageDebugInfo,
963        context: impl Fn() -> String,
964    ) -> Result<(), PackageDebugInfoError> {
965        if debug_info.get_string(index).is_none() {
966            return Err(PackageDebugInfoError::InvalidReference {
967                message: format!(
968                    "{} string index {index} is outside string table length {}",
969                    context(),
970                    debug_info.strings().len()
971                ),
972            });
973        }
974        Ok(())
975    }
976
977    fn validate_type_index(
978        &self,
979        index: DebugTypeIdx,
980        debug_info: &PackageDebugInfo,
981        context: impl Fn() -> String,
982    ) -> Result<(), PackageDebugInfoError> {
983        if debug_info.get_type(index).is_none() {
984            return Err(PackageDebugInfoError::InvalidReference {
985                message: format!(
986                    "{} type index {index} is outside type table length {}",
987                    context(),
988                    debug_info.types().len()
989                ),
990            });
991        }
992        Ok(())
993    }
994
995    fn validate_location_index(
996        &self,
997        index: crate::debug_info::DebugLocIdx,
998        debug_info: &PackageDebugInfo,
999        context: impl Fn() -> String,
1000    ) -> Result<(), PackageDebugInfoError> {
1001        if debug_info.get_location(index).is_none() {
1002            return Err(PackageDebugInfoError::InvalidReference {
1003                message: format!(
1004                    "{} index {index} is outside debug source location table length {}",
1005                    context(),
1006                    debug_info.locations().len(),
1007                ),
1008            });
1009        }
1010        Ok(())
1011    }
1012
1013    fn validate_source_map_row(
1014        &self,
1015        source_node_id: DebugSourceNodeId,
1016        source_node: &DebugSourceNode,
1017        op_idx: u32,
1018        row_kind: &'static str,
1019    ) -> Result<(), PackageDebugInfoError> {
1020        if op_idx < source_node.op_start || op_idx >= source_node.op_end {
1021            return Err(PackageDebugInfoError::InvalidReference {
1022                message: format!(
1023                    "{row_kind} row for source node {source_node_id:?} has op index {op_idx}, outside source range {}..{}",
1024                    source_node.op_start, source_node.op_end,
1025                ),
1026            });
1027        }
1028        Ok(())
1029    }
1030}
1031
1032fn read_section_payload<T>(id: &SectionId, bytes: &[u8]) -> Result<T, PackageDebugInfoError>
1033where
1034    T: Deserializable,
1035{
1036    let mut reader = SliceReader::new(bytes);
1037    let section = T::read_from(&mut reader)
1038        .map_err(|source| PackageDebugInfoError::DecodeSection { id: id.clone(), source })?;
1039    if reader.has_more_bytes() {
1040        return Err(PackageDebugInfoError::TrailingBytes { id: id.clone() });
1041    }
1042    Ok(section)
1043}
1044
1045/// Conversions
1046impl Package {
1047    /// Get a [Kernel] from this package, if this package contains one.
1048    pub fn to_kernel(&self) -> Result<Kernel, Report> {
1049        let exports = self
1050            .manifest
1051            .exports()
1052            .filter_map(|export| {
1053                if export.namespace().is_kernel_path()
1054                    && let PackageExport::Procedure(p) = export
1055                {
1056                    Some(p.digest)
1057                } else {
1058                    None
1059                }
1060            })
1061            .collect::<Vec<_>>();
1062        if exports.is_empty() {
1063            return Err(Report::msg(
1064                "invalid kernel package: does not export any kernel procedures",
1065            ));
1066        }
1067        Kernel::new(&exports).map_err(|err| Report::msg(format!("invalid kernel package: {err}")))
1068    }
1069
1070    // TODO(pauls): This function can be removed when we remove Program
1071    #[doc(hidden)]
1072    pub fn try_into_program(&self) -> Result<miden_core::program::Program, Report> {
1073        use miden_assembly_syntax::{Path as MasmPath, ast};
1074        use miden_core::program::Program;
1075
1076        if !self.is_program() {
1077            return Err(Report::msg(format!(
1078                "cannot convert package of type {} to Executable",
1079                self.kind
1080            )));
1081        }
1082        let entrypoint = self.manifest.entrypoint().unwrap_or_else(|| {
1083            MasmPath::exec_path().join(ast::ProcedureName::MAIN_PROC_NAME).into()
1084        });
1085        if let Some(entrypoint) = self.get_procedure_node_by_path(&entrypoint) {
1086            let mast_forest = self.mast.clone();
1087            let kernel_dependency = self.kernel_runtime_dependency()?.cloned();
1088            match (self.try_embedded_kernel_package()?, kernel_dependency) {
1089                (Some(kernel_package), _) => {
1090                    Ok(Program::with_kernel(mast_forest, entrypoint, kernel_package.to_kernel()?))
1091                },
1092                (None, Some(kernel_dependency)) => Err(Report::msg(format!(
1093                    "package '{}' declares kernel runtime dependency '{}@{}#{}', but does not embed the kernel package required to reconstruct a program",
1094                    self.name,
1095                    kernel_dependency.name,
1096                    kernel_dependency.version,
1097                    kernel_dependency.digest
1098                ))),
1099                (None, None) => Ok(Program::new(mast_forest, entrypoint)),
1100            }
1101        } else {
1102            Err(Report::msg(format!(
1103                "malformed executable package: no procedure root for '{entrypoint}'"
1104            )))
1105        }
1106    }
1107
1108    // TODO(pauls): This function can be removed when we remove Program
1109    #[doc(hidden)]
1110    pub fn unwrap_program(&self) -> miden_core::program::Program {
1111        assert_eq!(self.kind, TargetType::Executable);
1112        self.try_into_program().unwrap_or_else(|err| panic!("{err}"))
1113    }
1114
1115    /// Extract the embedded kernel package from this package.
1116    ///
1117    /// Returns `Ok(None)` if the kernel custom section is not present.
1118    ///
1119    /// Returns an error if:
1120    ///
1121    /// * The embedded package is not a kernel
1122    /// * The package manifest of `self` does not declare a kernel dependency
1123    /// * The embedded kernel does not match the declared kernel dependency
1124    pub fn try_embedded_kernel_package(&self) -> Result<Option<Box<Self>>, Report> {
1125        let Some(kernel_package) = self.embedded_kernel_package()? else {
1126            return Ok(None);
1127        };
1128        self.validate_embedded_kernel_dependency(&kernel_package)?;
1129        Ok(Some(kernel_package))
1130    }
1131
1132    /// This function extracts a embedded kernel package from the KERNEL section of this package,
1133    /// if present.
1134    ///
1135    /// This returns an error in the following situations:
1136    ///
1137    /// * There are duplicate KERNEL sections
1138    /// * Deserialization of a package from the KERNEL section fails
1139    fn embedded_kernel_package(&self) -> Result<Option<Box<Self>>, Report> {
1140        let mut sections = self.sections.iter().filter(|section| section.id == SectionId::KERNEL);
1141        let Some(section) = sections.next() else {
1142            return Ok(None);
1143        };
1144        if sections.next().is_some() {
1145            return Err(Report::msg(format!(
1146                "package '{}' contains multiple '{}' sections",
1147                self.name,
1148                SectionId::KERNEL
1149            )));
1150        }
1151
1152        if self.debug_sections_trusted {
1153            Self::read_from_bytes_trusted(section.data.as_ref())
1154        } else {
1155            Self::read_from_bytes(section.data.as_ref())
1156        }
1157        .map(Box::new)
1158        .map(Some)
1159        .map_err(|error| {
1160            Report::msg(format!(
1161                "failed to decode embedded kernel package for '{}': {error}",
1162                self.name
1163            ))
1164        })
1165    }
1166
1167    fn validate_embedded_kernel_dependency(&self, kernel_package: &Self) -> Result<(), Report> {
1168        if !kernel_package.is_kernel() {
1169            return Err(Report::msg(format!(
1170                "package '{}' embeds '{}', but its kind is '{}'",
1171                self.name, kernel_package.name, kernel_package.kind
1172            )));
1173        }
1174
1175        let Some(kernel_dependency) = self.kernel_runtime_dependency()? else {
1176            return Err(Report::msg(format!(
1177                "package '{}' embeds a kernel package, but does not declare a kernel runtime dependency",
1178                self.name
1179            )));
1180        };
1181
1182        if kernel_dependency.name != kernel_package.name
1183            || kernel_dependency.version != kernel_package.version
1184            || kernel_dependency.digest != kernel_package.digest()
1185        {
1186            return Err(Report::msg(format!(
1187                "package '{}' declares kernel runtime dependency '{}@{}#{}', but that does not match the embedded kernel package '{}@{}#{}'",
1188                self.name,
1189                kernel_dependency.name,
1190                kernel_dependency.version,
1191                kernel_dependency.digest,
1192                kernel_package.name,
1193                kernel_package.version,
1194                kernel_package.digest()
1195            )));
1196        }
1197
1198        Ok(())
1199    }
1200
1201    /// Get a [Dependency] that represents this package
1202    pub fn to_dependency(&self) -> Dependency {
1203        Dependency {
1204            name: self.name.clone(),
1205            version: self.version.clone(),
1206            kind: self.kind,
1207            digest: self.digest(),
1208        }
1209    }
1210
1211    /// Derive a new executable package from this one by specifying the entrypoint to use.
1212    ///
1213    /// To succeed, the following must be true:
1214    ///
1215    /// * This package was produced from a library target
1216    /// * The `entrypoint` procedure is exported from this package according to the manifest
1217    /// * The `entrypoint` procedure can be resolved to a node in the MAST of this package
1218    ///
1219    /// The resulting package has a target type and manifest reflecting what would have been used
1220    /// if the package was originally assembled as an executable, however the underlying
1221    /// [miden_core::mast::MastForest] is left untouched, so the resulting package may still contain
1222    /// nodes in the forest which are now unused.
1223    pub fn make_executable(&self, entrypoint: &QualifiedProcedureName) -> Result<Self, Report> {
1224        use miden_assembly_syntax::Path as MasmPath;
1225        if !self.is_library() {
1226            return Err(Report::msg("expected library but got an executable"));
1227        }
1228
1229        let entrypoint =
1230            Arc::<MasmPath>::from(entrypoint.to_absolute().map_err(Report::msg)?.to_path_buf());
1231        if let Some(export) = self.get_export_by_lookup_path(&entrypoint) {
1232            match export {
1233                PackageExport::Constant(_) | PackageExport::Type(_) => {
1234                    let actual = match export {
1235                        PackageExport::Constant(_) => "constant",
1236                        PackageExport::Type(_) => "type",
1237                        _ => unreachable!(),
1238                    };
1239                    Err(Report::msg(ManifestValidationError::UnexpectedExportType {
1240                        path: entrypoint,
1241                        expected: "procedure",
1242                        actual,
1243                    }))
1244                },
1245                PackageExport::Procedure(procedure) => {
1246                    let executable_entrypoint: Arc<MasmPath> =
1247                        MasmPath::exec_path().join(ast::ProcedureName::MAIN_PROC_NAME).into();
1248                    let mut procedure = procedure.clone();
1249                    procedure.path = executable_entrypoint;
1250                    let mut package = Self::create(
1251                        self.name.clone(),
1252                        self.version.clone(),
1253                        TargetType::Executable,
1254                        self.mast.clone(),
1255                        [PackageExport::Procedure(procedure)],
1256                        self.manifest.dependencies.clone(),
1257                    )
1258                    .map_err(Report::msg)?;
1259                    package.description = self.description.clone();
1260                    package.sections = self.sections.clone();
1261                    package.debug_sections_trusted = self.debug_sections_trusted;
1262                    Ok(package)
1263                },
1264            }
1265        } else {
1266            Err(Report::msg(format!(
1267                "invalid entrypoint: library does not export '{entrypoint}'"
1268            )))
1269        }
1270    }
1271}
1272
1273/// Serialization
1274impl Package {
1275    /// Write this package to `path`
1276    #[cfg(feature = "std")]
1277    pub fn write_to_file(&self, path: impl AsRef<std::path::Path>) -> std::io::Result<()> {
1278        use miden_core::serde::Serializable;
1279
1280        let path = path.as_ref();
1281        if let Some(dir) = path.parent() {
1282            std::fs::create_dir_all(dir)?;
1283        }
1284
1285        let mut file = std::fs::File::create(path)?;
1286        <Self as Serializable>::write_into(self, &mut file);
1287        Ok(())
1288    }
1289
1290    /// Write this package to a file in `dir` named `$name.masp`, where `$name` is the package name.
1291    #[cfg(feature = "std")]
1292    pub fn write_masp_file(&self, dir: impl AsRef<std::path::Path>) -> std::io::Result<()> {
1293        let dir = dir.as_ref();
1294        let package_name: &str = &self.name;
1295        self.write_to_file(dir.join(package_name).with_extension(Self::EXTENSION))
1296            .map_err(|err| std::io::Error::other(err.to_string()))
1297    }
1298
1299    #[cfg(feature = "std")]
1300    /// Reads a package file from an untrusted path.
1301    ///
1302    /// This validates the embedded MAST forest and discards package-owned debug sections before
1303    /// returning the package. Use this for user-provided paths or bytes received across a trust
1304    /// boundary.
1305    pub fn deserialize_from_file(
1306        path: impl AsRef<std::path::Path>,
1307    ) -> Result<Self, DeserializationError> {
1308        let bytes = read_package_file(path)?;
1309        Self::read_from_bytes(&bytes)
1310    }
1311
1312    #[cfg(feature = "std")]
1313    /// Reads a trusted local package file.
1314    ///
1315    /// This preserves package-owned debug sections and should be used only for files/cache entries
1316    /// controlled by the same trusted build or execution system. Use [`Self::read_from_bytes`] for
1317    /// bytes received across a trust boundary.
1318    pub fn deserialize_from_file_trusted(
1319        path: impl AsRef<std::path::Path>,
1320    ) -> Result<Self, DeserializationError> {
1321        let bytes = read_package_file(path)?;
1322        Self::read_from_bytes_trusted(&bytes)
1323    }
1324}
1325
1326#[cfg(feature = "std")]
1327fn read_package_file(path: impl AsRef<std::path::Path>) -> Result<Vec<u8>, DeserializationError> {
1328    let path = path.as_ref();
1329    std::fs::read(path).map_err(|err| {
1330        DeserializationError::InvalidValue(format!(
1331            "failed to open file at {}: {err}",
1332            path.to_string_lossy()
1333        ))
1334    })
1335}
1336
1337// TESTS
1338// ================================================================================================
1339
1340#[cfg(test)]
1341mod tests {
1342    use alloc::{sync::Arc, vec, vec::Vec};
1343    use core::{assert_matches, str::FromStr};
1344
1345    use miden_assembly_syntax::ast::{
1346        DebugVarLocation, Path as AstPath, PathBuf, ProcedureName, QualifiedProcedureName,
1347    };
1348    use miden_core::{
1349        Felt, Word,
1350        advice::AdviceMap,
1351        mast::{
1352            BasicBlockNodeBuilder, DenseMastForestBuilder, ExternalNodeBuilder, MastForest,
1353            MastNode, MastNodeExt, MastNodeId, SplitNodeBuilder,
1354        },
1355        operations::Operation,
1356        serde::Serializable,
1357        utils::IndexVec,
1358    };
1359    use miden_debug_types::{ByteIndex, ColumnNumber, LineNumber, Uri};
1360
1361    use super::*;
1362    use crate::{
1363        Dependency, Version,
1364        debug_info::{
1365            DebugFileIdx, DebugFunctionIdx, DebugFunctionInfo, DebugLoc, DebugLocIdx,
1366            DebugSourceAsmOp, DebugSourceInlineCall, DebugSourceNode, DebugSourceNodeId,
1367            DebugSourceVar, DebugStringIdx, DebugTypeIdx, DebugTypeInfo, PackageDebugInfoBuilder,
1368        },
1369    };
1370
1371    fn debug_source_node(
1372        exec_node: MastNodeId,
1373        children: Vec<DebugSourceNodeId>,
1374        op_start: u32,
1375        op_end: u32,
1376    ) -> DebugSourceNode {
1377        DebugSourceNode {
1378            exec_node,
1379            children,
1380            op_start,
1381            op_end,
1382            asm_ops: Vec::new(),
1383            debug_vars: Vec::new(),
1384            inline_calls: Vec::new(),
1385        }
1386    }
1387
1388    fn debug_info_section(debug_info: &PackageDebugInfo) -> Section {
1389        Section::new(SectionId::DEBUG_INFO, debug_info.to_bytes())
1390    }
1391
1392    fn assert_invalid_debug_reference(
1393        package: &mut Package,
1394        debug_info: &PackageDebugInfo,
1395        expected_message: &str,
1396    ) {
1397        package.sections = vec![debug_info_section(debug_info)];
1398        let error = package.debug_info().expect_err("invalid debug reference should be rejected");
1399        let PackageDebugInfoError::InvalidReference { message } = error else {
1400            panic!("unexpected validation result: {error:?}");
1401        };
1402        assert!(
1403            message.contains(expected_message),
1404            "expected {message:?} to contain {expected_message:?}"
1405        );
1406    }
1407
1408    fn build_forest() -> (MastForest, MastNodeId) {
1409        let mut builder = DenseMastForestBuilder::new();
1410        let node_id = builder
1411            .push_node(BasicBlockNodeBuilder::new(vec![Operation::Add]))
1412            .expect("failed to build basic block");
1413        builder.mark_root(node_id);
1414        let (forest, remapping) = builder.finish_with_id_map().expect("failed to build forest");
1415        let node_id = remapping.get(node_id).expect("root node should be retained");
1416        (forest, node_id)
1417    }
1418
1419    fn build_split_forest() -> (MastForest, MastNodeId, MastNodeId, MastNodeId) {
1420        let mut builder = DenseMastForestBuilder::new();
1421        let left_id = builder
1422            .push_node(BasicBlockNodeBuilder::new(vec![Operation::Add]))
1423            .expect("failed to build left basic block");
1424        let right_id = builder
1425            .push_node(BasicBlockNodeBuilder::new(vec![Operation::Mul]))
1426            .expect("failed to build right basic block");
1427        let root_id = builder
1428            .push_node(SplitNodeBuilder::new([left_id, right_id]))
1429            .expect("failed to build split node");
1430        builder.mark_root(root_id);
1431        let (forest, remapping) = builder.finish_with_id_map().expect("failed to build forest");
1432        let root_id = remapping.get(root_id).expect("root node should be retained");
1433        let left_id = remapping.get(left_id).expect("left node should be retained");
1434        let right_id = remapping.get(right_id).expect("right node should be retained");
1435        (forest, root_id, left_id, right_id)
1436    }
1437
1438    fn absolute_path(name: &str) -> Arc<AstPath> {
1439        let path = PathBuf::new(name).expect("invalid path");
1440        let path = path.as_path().to_absolute().unwrap().into_owned();
1441        Arc::from(path.into_boxed_path())
1442    }
1443
1444    fn relative_path(name: &str) -> Arc<AstPath> {
1445        let path = PathBuf::relative(name);
1446        Arc::from(path.into_boxed_path())
1447    }
1448
1449    fn build_package_exports(export: &str) -> (Arc<MastForest>, Vec<PackageExport>) {
1450        let (forest, node_id) = build_forest();
1451        let root = forest[node_id].digest();
1452        let path = absolute_path(export);
1453        let export = ProcedureExport::new(Arc::clone(&path), Some(node_id), root, None);
1454
1455        (Arc::new(forest), vec![PackageExport::Procedure(export)])
1456    }
1457
1458    fn build_split_package_exports(
1459        export: &str,
1460        source_node: Option<DebugSourceNodeId>,
1461    ) -> (Arc<MastForest>, Vec<PackageExport>, MastNodeId, MastNodeId, MastNodeId) {
1462        let (forest, root_id, left_id, right_id) = build_split_forest();
1463        let root = forest[root_id].digest();
1464        let path = absolute_path(export);
1465        let export = ProcedureExport::new(Arc::clone(&path), Some(root_id), root, None)
1466            .with_source_node(source_node);
1467
1468        (
1469            Arc::new(forest),
1470            vec![PackageExport::Procedure(export)],
1471            root_id,
1472            left_id,
1473            right_id,
1474        )
1475    }
1476
1477    fn build_same_digest_package_exports(
1478        exports: &[(&str, &str)],
1479    ) -> (Arc<MastForest>, Vec<PackageExport>, Vec<Section>) {
1480        let mut nodes = IndexVec::<MastNodeId, MastNode>::new();
1481        let mut roots = Vec::new();
1482        let mut new_exports = vec![];
1483        let mut debug_info = PackageDebugInfoBuilder::default();
1484
1485        for (source_idx, (path_str, context_name)) in exports.iter().enumerate() {
1486            let node = BasicBlockNodeBuilder::new(vec![Operation::Add])
1487                .build()
1488                .expect("failed to build basic block");
1489            let num_ops = node.num_operations();
1490            let digest = node.digest();
1491            let node_id = nodes.push(node.into()).expect("failed to add basic block");
1492            let context_name_idx = debug_info.add_string(*context_name);
1493            let op_name_idx = debug_info.add_string("add");
1494            let source_node = debug_info
1495                .add_node(DebugSourceNode {
1496                    exec_node: node_id,
1497                    children: Vec::new(),
1498                    op_start: 0,
1499                    op_end: num_ops,
1500                    asm_ops: vec![DebugSourceAsmOp::new(0, None, context_name_idx, op_name_idx, 1)],
1501                    debug_vars: Vec::new(),
1502                    inline_calls: Vec::new(),
1503                })
1504                .expect("failed to add debug source node");
1505            assert_eq!(source_node, DebugSourceNodeId::from(source_idx as u32));
1506            debug_info.add_root(source_node);
1507            roots.push(node_id);
1508
1509            let path = absolute_path(path_str);
1510            new_exports.push(PackageExport::Procedure(
1511                ProcedureExport::new(path, Some(node_id), digest, None)
1512                    .with_source_node(Some(source_node)),
1513            ));
1514        }
1515
1516        let debug_info = debug_info.build();
1517        let sections = vec![debug_info_section(debug_info.as_ref())];
1518
1519        let forest = MastForest::from_raw_parts(nodes, roots, AdviceMap::default())
1520            .expect("failed to build forest");
1521        (Arc::new(forest), new_exports, sections)
1522    }
1523
1524    fn build_package(
1525        name: &str,
1526        kind: TargetType,
1527        export: &str,
1528        dependencies: impl IntoIterator<Item = Dependency>,
1529        sections: Vec<Section>,
1530    ) -> Package {
1531        let (mast, exports) = build_package_exports(export);
1532        let mut package = Package::create(
1533            PackageId::from(name),
1534            Version::new(1, 0, 0),
1535            kind,
1536            mast,
1537            exports,
1538            dependencies,
1539        )
1540        .unwrap();
1541        package.sections = sections;
1542        package
1543    }
1544
1545    fn build_kernel_package(name: &str) -> Package {
1546        build_package(name, TargetType::Kernel, &format!("{name}::boot"), [], Vec::new())
1547    }
1548
1549    #[test]
1550    fn package_digest_changes_when_advice_map_changes() {
1551        let package = build_kernel_package("kernel");
1552        let package_digest = package.digest();
1553        let interface_digest = package.interface_digest().unwrap();
1554        let content_digest = package.content_digest();
1555        let mast_commitment = package.mast_forest().commitment();
1556
1557        let advice_map = AdviceMap::from_iter([(
1558            Word::from([1_u32, 2, 3, 4]),
1559            vec![Felt::from_u32(5), Felt::from_u32(6)],
1560        )]);
1561        let with_advice = package.with_advice_map(advice_map);
1562
1563        assert_ne!(package_digest, with_advice.digest());
1564        assert_eq!(interface_digest, with_advice.interface_digest().unwrap());
1565        assert_ne!(content_digest, with_advice.content_digest());
1566        assert_ne!(mast_commitment, with_advice.mast_forest().commitment());
1567    }
1568
1569    fn build_debug_package(name: &str, kind: TargetType, export: &str, context: &str) -> Package {
1570        let (mast, exports, sections) = build_same_digest_package_exports(&[(export, context)]);
1571        let mut package = Package::create(
1572            PackageId::from(name),
1573            Version::new(1, 0, 0),
1574            kind,
1575            mast,
1576            exports,
1577            None,
1578        )
1579        .unwrap();
1580        package.sections = sections;
1581        package
1582    }
1583
1584    fn debug_sections() -> Vec<Section> {
1585        vec![debug_info_section(&PackageDebugInfo::default())]
1586    }
1587
1588    #[test]
1589    fn package_without_debug_sections_has_no_package_debug_info() {
1590        let package = build_package("app", TargetType::Library, "app::entry", [], Vec::new());
1591
1592        assert!(package.debug_info().unwrap().is_none());
1593    }
1594
1595    #[test]
1596    fn package_debug_info_decodes_source_graph_and_map() {
1597        let mut package = build_package("app", TargetType::Library, "app::entry", [], Vec::new());
1598        let exec_node = package.get_export_node_id("app::entry");
1599        let mut builder = PackageDebugInfoBuilder::default();
1600        let context_name_idx = builder.add_string("app::entry");
1601        let op_name_idx = builder.add_string("add");
1602        let source_node = builder
1603            .add_node(DebugSourceNode {
1604                exec_node,
1605                children: Vec::new(),
1606                op_start: 0,
1607                op_end: 1,
1608                asm_ops: vec![DebugSourceAsmOp::new(0, None, context_name_idx, op_name_idx, 1)],
1609                debug_vars: Vec::new(),
1610                inline_calls: Vec::new(),
1611            })
1612            .unwrap();
1613        builder.add_root(source_node);
1614        let built_debug_info = builder.build();
1615        package.sections = vec![debug_info_section(built_debug_info.as_ref())];
1616
1617        let debug_info = package
1618            .debug_info()
1619            .expect("debug sections should decode")
1620            .expect("debug sections should be present");
1621
1622        assert_eq!(debug_info.source_node(source_node).unwrap().exec_node, exec_node);
1623        let asm_op = debug_info.asm_op_for_operation(source_node, 0).unwrap();
1624        assert_eq!(debug_info[asm_op.context_name_idx].as_ref(), "app::entry");
1625    }
1626
1627    #[test]
1628    fn package_debug_info_rejects_duplicate_debug_sections() {
1629        let mut package = build_package("app", TargetType::Library, "app::entry", [], Vec::new());
1630        package.sections = vec![
1631            debug_info_section(&PackageDebugInfo::default()),
1632            debug_info_section(&PackageDebugInfo::default()),
1633        ];
1634
1635        let error = package.debug_info().expect_err("duplicate debug sections should be rejected");
1636
1637        assert!(matches!(
1638            error,
1639            PackageDebugInfoError::DuplicateSection { id } if id == SectionId::DEBUG_INFO
1640        ));
1641    }
1642
1643    #[test]
1644    fn package_debug_info_rejects_malformed_debug_sections() {
1645        let mut package = build_package("app", TargetType::Library, "app::entry", [], Vec::new());
1646        package.sections = vec![Section::new(SectionId::DEBUG_INFO, vec![u8::MAX])];
1647
1648        let error = package.debug_info().expect_err("malformed debug sections should be rejected");
1649
1650        assert!(matches!(
1651            error,
1652            PackageDebugInfoError::DecodeSection { id, .. } if id == SectionId::DEBUG_INFO
1653        ));
1654    }
1655
1656    #[test]
1657    fn package_debug_info_rejects_invalid_non_source_graph_table_indices() {
1658        let mut package = build_package("app", TargetType::Library, "app::entry", [], Vec::new());
1659
1660        let mut builder = PackageDebugInfoBuilder::default();
1661        builder.add_type(DebugTypeInfo::Function {
1662            return_type_idx: Some(DebugTypeIdx::from(99)),
1663            param_type_indices: vec![DebugTypeIdx::from(99); 16],
1664        });
1665        let debug_info = builder.build();
1666        assert_invalid_debug_reference(&mut package, debug_info.as_ref(), "type index 99");
1667
1668        let mut builder = PackageDebugInfoBuilder::default();
1669        let file_idx = builder.add_file(Uri::new("app.masm"), None);
1670        let mut debug_info = builder.build();
1671        debug_info.set_file_path_index_for_test(file_idx, DebugStringIdx::from(99));
1672        assert_invalid_debug_reference(&mut package, debug_info.as_ref(), "string index 99");
1673
1674        let mut builder = PackageDebugInfoBuilder::default();
1675        let name_idx = builder.add_string("app::entry");
1676        builder.add_function(DebugFunctionInfo::new(
1677            None,
1678            name_idx,
1679            DebugFileIdx::from(99),
1680            LineNumber::new(1).unwrap(),
1681            ColumnNumber::new(1).unwrap(),
1682            Word::default(),
1683        ));
1684        let debug_info = builder.build();
1685        assert_invalid_debug_reference(
1686            &mut package,
1687            debug_info.as_ref(),
1688            "file index 99 is outside debug source file table length 0",
1689        );
1690    }
1691
1692    #[test]
1693    fn package_debug_info_rejects_invalid_consolidated_table_references() {
1694        let mut package = build_package("app", TargetType::Library, "app::entry", [], Vec::new());
1695        let exec_node = package.get_export_node_id("app::entry");
1696
1697        let mut builder = PackageDebugInfoBuilder::default();
1698        let file_idx = builder.add_file(Uri::new("app.masm"), None);
1699        let location_idx = builder.add_location_info(DebugLoc {
1700            file_idx,
1701            start: ByteIndex::new(0),
1702            end: ByteIndex::new(1),
1703        });
1704        let mut debug_info = builder.build();
1705        debug_info.set_location_file_index_for_test(location_idx, DebugFileIdx::from(99));
1706        assert_invalid_debug_reference(
1707            &mut package,
1708            debug_info.as_ref(),
1709            "location 0 file index 99",
1710        );
1711
1712        let mut builder = PackageDebugInfoBuilder::default();
1713        assert!(builder.add_error_message(7, Arc::from("invalid message index")));
1714        let mut debug_info = builder.build();
1715        debug_info.set_error_message_index_for_test(0, DebugStringIdx::from(99));
1716        assert_invalid_debug_reference(
1717            &mut package,
1718            debug_info.as_ref(),
1719            "debug error message 0 string index 99",
1720        );
1721
1722        let mut builder = PackageDebugInfoBuilder::default();
1723        let name_idx = builder.add_string("app::entry");
1724        let file_idx = builder.add_file(Uri::new("app.masm"), None);
1725        builder.add_function(DebugFunctionInfo::new(
1726            Some(DebugSourceNodeId::from(99)),
1727            name_idx,
1728            file_idx,
1729            LineNumber::new(1).unwrap(),
1730            ColumnNumber::new(1).unwrap(),
1731            Word::default(),
1732        ));
1733        let debug_info = builder.build();
1734        assert_invalid_debug_reference(
1735            &mut package,
1736            debug_info.as_ref(),
1737            "source node DebugSourceNodeId(99)",
1738        );
1739
1740        for (context_name_idx, op_name_idx, location_idx, expected) in [
1741            (
1742                DebugStringIdx::from(99),
1743                DebugStringIdx::from(0),
1744                None,
1745                "assembly op context name string index 99",
1746            ),
1747            (
1748                DebugStringIdx::from(0),
1749                DebugStringIdx::from(99),
1750                None,
1751                "assembly op name string index 99",
1752            ),
1753            (
1754                DebugStringIdx::from(0),
1755                DebugStringIdx::from(0),
1756                Some(DebugLocIdx::from(99)),
1757                "assembly op location index 99",
1758            ),
1759        ] {
1760            let mut builder = PackageDebugInfoBuilder::default();
1761            builder.add_string("valid");
1762            let mut node = debug_source_node(exec_node, Vec::new(), 0, 1);
1763            node.asm_ops.push(DebugSourceAsmOp::new(
1764                0,
1765                location_idx,
1766                context_name_idx,
1767                op_name_idx,
1768                1,
1769            ));
1770            builder.add_node(node).unwrap();
1771            let debug_info = builder.build();
1772            assert_invalid_debug_reference(&mut package, debug_info.as_ref(), expected);
1773        }
1774
1775        for (name_idx, type_id, location_idx, expected) in [
1776            (DebugStringIdx::from(99), None, None, "variable name string index 99"),
1777            (
1778                DebugStringIdx::from(0),
1779                Some(DebugTypeIdx::from(99)),
1780                None,
1781                "variable type index 99",
1782            ),
1783            (
1784                DebugStringIdx::from(0),
1785                None,
1786                Some(DebugLocIdx::from(99)),
1787                "variable location index 99",
1788            ),
1789        ] {
1790            let mut builder = PackageDebugInfoBuilder::default();
1791            builder.add_string("valid");
1792            let mut node = debug_source_node(exec_node, Vec::new(), 0, 1);
1793            node.debug_vars.push(DebugSourceVar {
1794                op_idx: 0,
1795                name_idx,
1796                type_id,
1797                arg_idx: None,
1798                location_idx,
1799                value_location: DebugVarLocation::Stack(0),
1800            });
1801            builder.add_node(node).unwrap();
1802            let debug_info = builder.build();
1803            assert_invalid_debug_reference(&mut package, debug_info.as_ref(), expected);
1804        }
1805    }
1806
1807    #[test]
1808    fn package_debug_info_rejects_invalid_inline_call_indices() {
1809        let mut package = build_package("app", TargetType::Library, "app::entry", [], Vec::new());
1810        let exec_node = package.get_export_node_id("app::entry");
1811
1812        let mut builder = PackageDebugInfoBuilder::default();
1813        let mut node = debug_source_node(exec_node, Vec::new(), 0, 1);
1814        node.inline_calls.push(DebugSourceInlineCall {
1815            op_idx: 0,
1816            callee_idx: DebugFunctionIdx::from(1),
1817            loc_idx: DebugLocIdx::from(0),
1818        });
1819        builder.add_node(node).unwrap();
1820        let debug_info = builder.build();
1821        package.sections = vec![debug_info_section(debug_info.as_ref())];
1822
1823        let err = package.debug_info().expect_err("bad inline call index should be rejected");
1824        assert!(matches!(err, PackageDebugInfoError::InvalidReference { .. }));
1825
1826        let mut builder = PackageDebugInfoBuilder::default();
1827        let file_idx = builder.add_file(Uri::new("app.masm"), None);
1828        let name_idx = builder.add_string("app::entry");
1829        let function_idx = builder.add_function(DebugFunctionInfo::new(
1830            None,
1831            name_idx,
1832            file_idx,
1833            LineNumber::new(1).unwrap(),
1834            ColumnNumber::new(1).unwrap(),
1835            Word::default(),
1836        ));
1837        let mut node = debug_source_node(exec_node, Vec::new(), 0, 1);
1838        node.inline_calls.push(DebugSourceInlineCall {
1839            op_idx: 0,
1840            callee_idx: function_idx,
1841            loc_idx: DebugLocIdx::from(99),
1842        });
1843        builder.add_node(node).unwrap();
1844        let debug_info = builder.build();
1845        package.sections = vec![debug_info_section(debug_info.as_ref())];
1846
1847        let err = package.debug_info().expect_err("bad inline call location should be rejected");
1848        assert!(matches!(err, PackageDebugInfoError::InvalidReference { .. }));
1849    }
1850
1851    #[test]
1852    fn package_debug_info_rejects_source_graph_child_exec_mismatch() {
1853        let source_left = DebugSourceNodeId::from(0);
1854        let source_right = DebugSourceNodeId::from(1);
1855        let source_root = DebugSourceNodeId::from(2);
1856        let (mast, exports, root_id, left_id, right_id) =
1857            build_split_package_exports("app::entry", Some(source_root));
1858        let mut package = Package::create(
1859            PackageId::from("app"),
1860            Version::new(1, 0, 0),
1861            TargetType::Library,
1862            mast,
1863            exports,
1864            None,
1865        )
1866        .unwrap();
1867        let mut builder = PackageDebugInfoBuilder::default();
1868        assert_eq!(
1869            builder.add_node(debug_source_node(left_id, Vec::new(), 0, 1)).unwrap(),
1870            source_left
1871        );
1872        assert_eq!(
1873            builder.add_node(debug_source_node(right_id, Vec::new(), 0, 1)).unwrap(),
1874            source_right
1875        );
1876        assert_eq!(
1877            builder
1878                .add_node(debug_source_node(root_id, vec![source_right, source_left], 0, 1,))
1879                .unwrap(),
1880            source_root
1881        );
1882        builder.add_root(source_root);
1883        let debug_info = builder.build();
1884        package.sections = vec![debug_info_section(debug_info.as_ref())];
1885
1886        let error = package.debug_info().expect_err("mismatched source child should be rejected");
1887
1888        assert!(matches!(error, PackageDebugInfoError::InvalidReference { .. }));
1889    }
1890
1891    #[test]
1892    fn package_debug_info_rejects_invalid_source_node_operation_ranges() {
1893        let mut package = build_package("app", TargetType::Library, "app::entry", [], Vec::new());
1894        let exec_node = package.get_export_node_id("app::entry");
1895        let source_node = DebugSourceNodeId::from(0);
1896
1897        for (op_start, op_end) in [(1, 0), (0, 2)] {
1898            let mut builder = PackageDebugInfoBuilder::default();
1899            let added_source_node =
1900                builder.add_node(debug_source_node(exec_node, Vec::new(), 0, 1)).unwrap();
1901            assert_eq!(added_source_node, source_node);
1902            builder[source_node].op_start = op_start;
1903            builder[source_node].op_end = op_end;
1904            builder.add_root(source_node);
1905            let debug_info = builder.build();
1906            package.sections = vec![debug_info_section(debug_info.as_ref())];
1907
1908            let error = package
1909                .debug_info()
1910                .expect_err("invalid source node operation range should be rejected");
1911
1912            assert!(matches!(error, PackageDebugInfoError::InvalidReference { .. }));
1913        }
1914    }
1915
1916    #[test]
1917    fn package_debug_info_rejects_export_source_node_exec_mismatch() {
1918        let source_left = DebugSourceNodeId::from(0);
1919        let source_right = DebugSourceNodeId::from(1);
1920        let source_root = DebugSourceNodeId::from(2);
1921        let (mast, exports, root_id, left_id, right_id) =
1922            build_split_package_exports("app::entry", Some(source_left));
1923        let mut package = Package::create(
1924            PackageId::from("app"),
1925            Version::new(1, 0, 0),
1926            TargetType::Library,
1927            mast,
1928            exports,
1929            None,
1930        )
1931        .unwrap();
1932        let mut builder = PackageDebugInfoBuilder::default();
1933        assert_eq!(
1934            builder.add_node(debug_source_node(left_id, Vec::new(), 0, 1)).unwrap(),
1935            source_left
1936        );
1937        assert_eq!(
1938            builder.add_node(debug_source_node(right_id, Vec::new(), 0, 1)).unwrap(),
1939            source_right
1940        );
1941        assert_eq!(
1942            builder
1943                .add_node(debug_source_node(root_id, vec![source_left, source_right], 0, 1))
1944                .unwrap(),
1945            source_root
1946        );
1947        builder.add_root(source_root);
1948        let debug_info = builder.build();
1949        package.sections = vec![debug_info_section(debug_info.as_ref())];
1950
1951        let error = package
1952            .debug_info()
1953            .expect_err("export source node mapped to child exec node should be rejected");
1954
1955        assert!(matches!(error, PackageDebugInfoError::InvalidReference { .. }));
1956    }
1957
1958    #[test]
1959    fn to_kernel_rejects_empty_kernel_exports() {
1960        let mut package = build_package("kernel", TargetType::Kernel, "$kernel::boot", [], vec![]);
1961        package.manifest = PackageManifest {
1962            exports: Default::default(),
1963            modules: Default::default(),
1964            dependencies: Default::default(),
1965            entrypoint: None,
1966        };
1967
1968        let error = package
1969            .to_kernel()
1970            .expect_err("kernel packages without exported procedures should be rejected");
1971
1972        assert!(
1973            error
1974                .to_string()
1975                .contains("invalid kernel package: does not export any kernel procedures")
1976        );
1977    }
1978
1979    fn kernel_dependency(package: &Package) -> Dependency {
1980        Dependency {
1981            name: package.name.clone(),
1982            kind: TargetType::Kernel,
1983            version: package.version.clone(),
1984            digest: package.digest(),
1985        }
1986    }
1987
1988    #[test]
1989    fn embedded_kernel_package_rejects_duplicate_kernel_sections() {
1990        let kernel = build_kernel_package("kernel");
1991        let kernel_bytes = kernel.to_bytes();
1992        let package = build_package(
1993            "app",
1994            TargetType::Library,
1995            "app::entry",
1996            vec![kernel_dependency(&kernel)],
1997            vec![
1998                Section::new(SectionId::KERNEL, kernel_bytes.clone()),
1999                Section::new(SectionId::KERNEL, kernel_bytes),
2000            ],
2001        );
2002
2003        let error = package
2004            .try_embedded_kernel_package()
2005            .expect_err("duplicate kernel sections should be rejected");
2006
2007        assert!(error.to_string().contains("multiple 'kernel' sections"));
2008    }
2009
2010    #[test]
2011    fn embedded_kernel_package_rejects_multiple_kernel_runtime_dependencies() {
2012        let kernel_a = build_kernel_package("kernel-a");
2013        let kernel_b = build_kernel_package("kernel-b");
2014        let package = build_package(
2015            "app",
2016            TargetType::Library,
2017            "app::entry",
2018            vec![kernel_dependency(&kernel_a), kernel_dependency(&kernel_b)],
2019            vec![Section::new(SectionId::KERNEL, kernel_a.to_bytes())],
2020        );
2021
2022        let error = package
2023            .try_embedded_kernel_package()
2024            .expect_err("multiple kernel runtime dependencies should be rejected");
2025
2026        assert!(error.to_string().contains("declares multiple kernel runtime dependencies"));
2027    }
2028
2029    #[test]
2030    fn untrusted_embedded_kernel_decode_discards_nested_debug_info() {
2031        let kernel =
2032            build_debug_package("kernel", TargetType::Kernel, "kernel::boot", "kernel_ctx");
2033        assert!(kernel.debug_info().unwrap().is_some());
2034
2035        let package = build_package(
2036            "app",
2037            TargetType::Executable,
2038            "app::entry",
2039            vec![kernel_dependency(&kernel)],
2040            vec![Section::new(SectionId::KERNEL, kernel.to_bytes())],
2041        );
2042
2043        let round_tripped = Package::read_from_bytes(&package.to_bytes())
2044            .expect("untrusted package read should succeed");
2045        let raw_kernel_bytes = round_tripped
2046            .sections
2047            .iter()
2048            .find(|section| section.id == SectionId::KERNEL)
2049            .expect("kernel section should remain available as opaque bytes")
2050            .data
2051            .as_ref();
2052        let trusted_kernel = Package::read_from_bytes_trusted(raw_kernel_bytes)
2053            .expect("trusted direct kernel read should succeed");
2054        assert!(
2055            trusted_kernel.debug_info().unwrap().is_some(),
2056            "opaque kernel bytes may still contain trusted-cache debug metadata"
2057        );
2058
2059        let untrusted_kernel = round_tripped
2060            .try_embedded_kernel_package()
2061            .expect("embedded kernel should decode")
2062            .expect("kernel should be present");
2063        assert!(
2064            !untrusted_kernel.sections.iter().any(|section| section.id.is_debug()),
2065            "untrusted embedded-kernel decode should discard nested debug sections"
2066        );
2067        assert!(untrusted_kernel.debug_info().unwrap().is_none());
2068    }
2069
2070    #[test]
2071    fn strip_debug_info_removes_package_and_embedded_kernel_debug() {
2072        let mut kernel =
2073            build_debug_package("kernel", TargetType::Kernel, "kernel::boot", "kernel_ctx");
2074        kernel.sections = debug_sections();
2075        kernel
2076            .sections
2077            .push(Section::new(SectionId::ACCOUNT_COMPONENT_METADATA, vec![42, 43, 44]));
2078        assert!(kernel.sections.iter().any(|section| section.id.is_debug()));
2079
2080        let mut package =
2081            build_debug_package("app", TargetType::Executable, "app::entry", "app_ctx");
2082        let digest = package.digest();
2083        package.sections = debug_sections();
2084        package
2085            .sections
2086            .push(Section::new(SectionId::ACCOUNT_COMPONENT_METADATA, vec![1, 3, 5]));
2087        package.sections.push(Section::new(SectionId::KERNEL, kernel.to_bytes()));
2088        let content_digest = package.content_digest();
2089        assert!(package.sections.iter().any(|section| section.id.is_debug()));
2090
2091        package.strip_debug_info().expect("strip should succeed");
2092
2093        assert_eq!(package.digest(), digest);
2094        assert_eq!(package.content_digest(), content_digest);
2095        assert!(!package.sections.iter().any(|section| section.id.is_debug()));
2096        assert!(
2097            package
2098                .sections
2099                .iter()
2100                .any(|section| section.id == SectionId::ACCOUNT_COMPONENT_METADATA)
2101        );
2102
2103        let stripped_kernel = package
2104            .embedded_kernel_package()
2105            .unwrap()
2106            .expect("kernel should remain embedded");
2107        assert!(!stripped_kernel.sections.iter().any(|section| section.id.is_debug()));
2108        assert!(
2109            stripped_kernel
2110                .sections
2111                .iter()
2112                .any(|section| section.id == SectionId::ACCOUNT_COMPONENT_METADATA)
2113        );
2114
2115        let raw_kernel_bytes = package
2116            .sections
2117            .iter()
2118            .find(|section| section.id == SectionId::KERNEL)
2119            .expect("kernel section should remain embedded")
2120            .data
2121            .as_ref();
2122        let trusted_stripped_kernel = Package::read_from_bytes_trusted(raw_kernel_bytes)
2123            .expect("trusted stripped kernel read should succeed");
2124        assert!(
2125            !trusted_stripped_kernel.sections.iter().any(|section| section.id.is_debug()),
2126            "stripping should remove nested debug sections from raw kernel bytes"
2127        );
2128    }
2129
2130    #[test]
2131    fn malformed_procedure_lookup_paths_are_not_exported() {
2132        let package = build_package("app", TargetType::Library, "app::entry", [], Vec::new());
2133        let invalid_path = alloc::format!("::{}", "a".repeat(AstPath::MAX_COMPONENT_LENGTH + 1));
2134        let invalid_path = AstPath::new(&invalid_path);
2135
2136        assert_eq!(package.get_procedure_root_by_path(invalid_path), None);
2137        assert_eq!(package.get_procedure_node_by_path(invalid_path), None);
2138        assert!(!package.is_reexport(invalid_path));
2139    }
2140
2141    #[test]
2142    fn procedure_lookup_accepts_relative_and_absolute_export_paths() {
2143        let (forest, node_id) = build_forest();
2144        let digest = forest[node_id].digest();
2145        let path = relative_path("app::entry");
2146        let export =
2147            PackageExport::Procedure(ProcedureExport::new(path, Some(node_id), digest, None));
2148        let package = Package::create(
2149            PackageId::from("app"),
2150            Version::new(1, 0, 0),
2151            TargetType::Library,
2152            Arc::new(forest),
2153            vec![export],
2154            None,
2155        )
2156        .expect("package should be valid");
2157
2158        assert_eq!(package.get_procedure_root_by_path("app::entry"), Some(digest));
2159        assert_eq!(package.get_procedure_root_by_path("::app::entry"), Some(digest));
2160        assert_eq!(package.get_procedure_node_by_path("app::entry"), Some(node_id));
2161        assert_eq!(package.get_procedure_node_by_path("::app::entry"), Some(node_id));
2162        assert_eq!(package.get_export_node_id("::app::entry"), node_id);
2163        assert!(!package.is_reexport("::app::entry"));
2164    }
2165
2166    #[test]
2167    fn make_executable_preserves_selected_same_digest_root_metadata() {
2168        let (mast, exports, sections) = build_same_digest_package_exports(&[
2169            ("app::alias_a", "alias_a"),
2170            ("app::alias_b", "alias_b"),
2171        ]);
2172        let mut package = Package::create(
2173            PackageId::from("app"),
2174            Version::new(1, 0, 0),
2175            TargetType::Library,
2176            mast,
2177            exports,
2178            None,
2179        )
2180        .expect("package should be valid");
2181        package.sections = sections;
2182
2183        let entrypoint = QualifiedProcedureName::from_str("app::alias_b").unwrap();
2184        let executable = package.make_executable(&entrypoint).unwrap();
2185
2186        let main_path = Path::exec_path().join(ProcedureName::MAIN_PROC_NAME);
2187        let entrypoint_node = executable.get_procedure_node_by_path(&main_path).unwrap();
2188        let main_export = executable
2189            .manifest
2190            .get_export(&main_path)
2191            .and_then(PackageExport::as_procedure)
2192            .expect("main export should exist");
2193        let source_node = main_export.source_node.expect("main export should retain source node");
2194        let debug_info = executable
2195            .debug_info()
2196            .expect("debug sections should decode")
2197            .expect("debug sections should be present");
2198
2199        assert_eq!(debug_info.source_node(source_node).unwrap().exec_node, entrypoint_node);
2200        let asm_op = debug_info.first_asm_op_for_source_node(source_node).unwrap();
2201        assert_eq!(debug_info[asm_op.context_name_idx].as_ref(), "alias_b");
2202
2203        let program = executable.try_into_program().unwrap();
2204        assert_eq!(program.entrypoint(), entrypoint_node);
2205    }
2206
2207    #[test]
2208    fn make_executable_preserves_debug_section_trust_state() {
2209        let (mast, exports, sections) = build_same_digest_package_exports(&[
2210            ("app::alias_a", "alias_a"),
2211            ("app::alias_b", "alias_b"),
2212        ]);
2213        let mut package = Package::create(
2214            PackageId::from("app"),
2215            Version::new(1, 0, 0),
2216            TargetType::Library,
2217            mast,
2218            exports,
2219            None,
2220        )
2221        .expect("package should be valid");
2222        package.sections = sections;
2223        package.debug_sections_trusted = false;
2224
2225        let executable = package
2226            .make_executable(&QualifiedProcedureName::from_str("app::alias_b").unwrap())
2227            .unwrap();
2228
2229        assert!(!executable.debug_sections_trusted);
2230        assert_matches!(executable.debug_info(), Err(PackageDebugInfoError::UntrustedSections));
2231    }
2232
2233    #[test]
2234    fn make_executable_accepts_relative_entrypoint_export_path() {
2235        let (forest, node_id) = build_forest();
2236        let digest = forest[node_id].digest();
2237        let path = relative_path("app::entry");
2238        let export =
2239            PackageExport::Procedure(ProcedureExport::new(path, Some(node_id), digest, None));
2240        let package = Package::create(
2241            PackageId::from("app"),
2242            Version::new(1, 0, 0),
2243            TargetType::Library,
2244            Arc::new(forest),
2245            [export],
2246            None,
2247        )
2248        .expect("package should be valid");
2249
2250        let entrypoint = QualifiedProcedureName::from_str("app::entry").unwrap();
2251        let executable = package.make_executable(&entrypoint).unwrap();
2252
2253        let main_path = Path::exec_path().join(ProcedureName::MAIN_PROC_NAME);
2254        assert_eq!(executable.get_procedure_root_by_path(&main_path), Some(digest));
2255        assert_eq!(executable.get_procedure_node_by_path(&main_path), Some(node_id));
2256    }
2257
2258    #[test]
2259    fn merge_source_debug_keeps_concrete_metadata_distinct_from_external_placeholder() {
2260        fn debug_info_for_root(root: MastNodeId, context: &str) -> PackageDebugInfo {
2261            let mut builder = PackageDebugInfoBuilder::default();
2262            let context_name_idx = builder.add_string(context);
2263            let op_name_idx = builder.add_string("add");
2264            let source_node = builder
2265                .add_node(DebugSourceNode {
2266                    exec_node: root,
2267                    children: Vec::new(),
2268                    op_start: 0,
2269                    op_end: 1,
2270                    asm_ops: vec![DebugSourceAsmOp::new(0, None, context_name_idx, op_name_idx, 1)],
2271                    debug_vars: Vec::new(),
2272                    inline_calls: Vec::new(),
2273                })
2274                .unwrap();
2275            builder.add_root(source_node);
2276            *builder.build()
2277        }
2278
2279        let mut concrete_builder = DenseMastForestBuilder::new();
2280        let concrete_root = concrete_builder
2281            .push_node(BasicBlockNodeBuilder::new(vec![Operation::Add]))
2282            .unwrap();
2283        concrete_builder.mark_root(concrete_root);
2284        let (concrete_forest, concrete_remapping) = concrete_builder.finish_with_id_map().unwrap();
2285        let concrete_root = concrete_remapping.get(concrete_root).unwrap();
2286        let concrete_digest = concrete_forest[concrete_root].digest();
2287
2288        let mut placeholder_builder = DenseMastForestBuilder::new();
2289        let placeholder_root = placeholder_builder
2290            .push_node(ExternalNodeBuilder::new(concrete_digest))
2291            .unwrap();
2292        placeholder_builder.mark_root(placeholder_root);
2293        let (placeholder_forest, placeholder_remapping) =
2294            placeholder_builder.finish_with_id_map().unwrap();
2295        let placeholder_root = placeholder_remapping.get(placeholder_root).unwrap();
2296
2297        let placeholder_debug = debug_info_for_root(placeholder_root, "placeholder");
2298        let concrete_debug = debug_info_for_root(concrete_root, "concrete");
2299
2300        let (_merged_forest, root_map) =
2301            MastForest::merge([&placeholder_forest, &concrete_forest]).unwrap();
2302        let merged_placeholder = root_map.map_root(0, &placeholder_root).unwrap();
2303        let merged_concrete = root_map.map_root(1, &concrete_root).unwrap();
2304        assert_eq!(merged_placeholder, merged_concrete);
2305
2306        let merged_debug = PackageDebugInfo::merge_source_debug(
2307            [(0, &placeholder_debug), (1, &concrete_debug)],
2308            &root_map,
2309        )
2310        .unwrap();
2311        assert_eq!(merged_debug.nodes().len(), 2);
2312        assert!(merged_debug.nodes().iter().all(|node| node.exec_node == merged_concrete));
2313
2314        let placeholder_source = merged_debug.roots()[0];
2315        let concrete_source = merged_debug.roots()[1];
2316        assert_ne!(placeholder_source, concrete_source);
2317        let placeholder_op = merged_debug.first_asm_op_for_source_node(placeholder_source).unwrap();
2318        assert_eq!(merged_debug[placeholder_op.context_name_idx].as_ref(), "placeholder",);
2319        let concrete_op = merged_debug.first_asm_op_for_source_node(concrete_source).unwrap();
2320        assert_eq!(merged_debug[concrete_op.context_name_idx].as_ref(), "concrete",);
2321    }
2322
2323    #[test]
2324    fn make_executable_same_digest_selection_is_export_order_independent() {
2325        fn selected_context_for_alias_b(exports: &[(&str, &str)]) -> String {
2326            let (mast, exports, sections) = build_same_digest_package_exports(exports);
2327            let mut package = Package::create(
2328                PackageId::from("app"),
2329                Version::new(1, 0, 0),
2330                TargetType::Library,
2331                mast,
2332                exports,
2333                None,
2334            )
2335            .expect("package should be valid");
2336            package.sections = sections;
2337
2338            let executable = package
2339                .make_executable(&QualifiedProcedureName::from_str("app::alias_b").unwrap())
2340                .unwrap();
2341            let main_path = Path::exec_path().join(ProcedureName::MAIN_PROC_NAME);
2342            let main_export = executable
2343                .manifest
2344                .get_export(&main_path)
2345                .and_then(PackageExport::as_procedure)
2346                .expect("main export should exist");
2347            let source_node =
2348                main_export.source_node.expect("main export should retain source node");
2349            let debug_info = executable
2350                .debug_info()
2351                .expect("debug sections should decode")
2352                .expect("debug sections should be present");
2353
2354            let asm_op = debug_info.first_asm_op_for_source_node(source_node).unwrap();
2355            debug_info[asm_op.context_name_idx].to_string()
2356        }
2357
2358        assert_eq!(
2359            selected_context_for_alias_b(&[
2360                ("app::alias_a", "alias_a"),
2361                ("app::alias_b", "alias_b")
2362            ]),
2363            "alias_b",
2364        );
2365        assert_eq!(
2366            selected_context_for_alias_b(&[
2367                ("app::alias_b", "alias_b"),
2368                ("app::alias_a", "alias_a")
2369            ]),
2370            "alias_b",
2371        );
2372    }
2373}