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