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