Skip to main content

miden_mast_package/package/
mod.rs

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