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