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