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