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