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