sui_adapter_latest/static_programmable_transactions/
env.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4//! This module defines the shared environment, `Env`, used for the compilation/translation and
5//! execution of programmable transactions. While the "context" for each pass might be different,
6//! the `Env` provides consistent access to shared components such as the VM or the protocol config.
7
8use crate::{
9    data_store::{
10        PackageStore, cached_package_store::CachedPackageStore, linked_data_store::LinkedDataStore,
11    },
12    execution_value::ExecutionState,
13    programmable_transactions::execution::subst_signature,
14    static_programmable_transactions::{
15        linkage::{
16            analysis::LinkageAnalyzer,
17            resolved_linkage::{ResolvedLinkage, RootedLinkage},
18        },
19        loading::ast::{
20            self as L, Datatype, LoadedFunction, LoadedFunctionInstantiation, Type, Vector,
21        },
22    },
23};
24use move_binary_format::{
25    CompiledModule,
26    errors::{Location, PartialVMError, VMError},
27    file_format::{AbilitySet, TypeParameterIndex},
28};
29use move_core_types::{
30    annotated_value,
31    language_storage::{ModuleId, StructTag},
32    runtime_value::{self, MoveTypeLayout},
33    vm_status::StatusCode,
34};
35use move_vm_runtime::move_vm::MoveVM;
36use move_vm_types::{data_store::DataStore, loaded_data::runtime_types as vm_runtime_type};
37use std::{cell::OnceCell, rc::Rc, sync::Arc};
38use sui_protocol_config::ProtocolConfig;
39use sui_types::{
40    Identifier, TypeTag,
41    base_types::{ObjectID, TxContext},
42    error::{ExecutionError, ExecutionErrorKind},
43    execution_status::TypeArgumentError,
44    gas_coin::GasCoin,
45    move_package::{UpgradeCap, UpgradeReceipt, UpgradeTicket},
46    object::Object,
47    type_input::{StructInput, TypeInput},
48};
49
50pub struct Env<'pc, 'vm, 'state, 'linkage> {
51    pub protocol_config: &'pc ProtocolConfig,
52    pub vm: &'vm MoveVM,
53    pub state_view: &'state mut dyn ExecutionState,
54    pub linkable_store: &'linkage CachedPackageStore<'state>,
55    pub linkage_analysis: &'linkage LinkageAnalyzer,
56    gas_coin_type: OnceCell<Type>,
57    upgrade_ticket_type: OnceCell<Type>,
58    upgrade_receipt_type: OnceCell<Type>,
59    upgrade_cap_type: OnceCell<Type>,
60    tx_context_type: OnceCell<Type>,
61}
62
63macro_rules! get_or_init_ty {
64    ($env:expr, $ident:ident, $tag:expr) => {{
65        let env = $env;
66        if env.$ident.get().is_none() {
67            let tag = $tag;
68            let ty = env.load_type_from_struct(&tag)?;
69            env.$ident.set(ty.clone()).unwrap();
70        }
71        Ok(env.$ident.get().unwrap().clone())
72    }};
73}
74
75impl<'pc, 'vm, 'state, 'linkage> Env<'pc, 'vm, 'state, 'linkage> {
76    pub fn new(
77        protocol_config: &'pc ProtocolConfig,
78        vm: &'vm MoveVM,
79        state_view: &'state mut dyn ExecutionState,
80        linkable_store: &'linkage CachedPackageStore<'state>,
81        linkage_analysis: &'linkage LinkageAnalyzer,
82    ) -> Self {
83        Self {
84            protocol_config,
85            vm,
86            state_view,
87            linkable_store,
88            linkage_analysis,
89            gas_coin_type: OnceCell::new(),
90            upgrade_ticket_type: OnceCell::new(),
91            upgrade_receipt_type: OnceCell::new(),
92            upgrade_cap_type: OnceCell::new(),
93            tx_context_type: OnceCell::new(),
94        }
95    }
96
97    pub fn convert_linked_vm_error(&self, e: VMError, linkage: &RootedLinkage) -> ExecutionError {
98        convert_vm_error(e, self.vm, self.linkable_store, Some(linkage))
99    }
100
101    pub fn convert_vm_error(&self, e: VMError) -> ExecutionError {
102        convert_vm_error(e, self.vm, self.linkable_store, None)
103    }
104
105    pub fn convert_type_argument_error(
106        &self,
107        idx: usize,
108        e: VMError,
109        linkage: &RootedLinkage,
110    ) -> ExecutionError {
111        use move_core_types::vm_status::StatusCode;
112        match e.major_status() {
113            StatusCode::NUMBER_OF_TYPE_ARGUMENTS_MISMATCH => {
114                ExecutionErrorKind::TypeArityMismatch.into()
115            }
116            StatusCode::TYPE_RESOLUTION_FAILURE => ExecutionErrorKind::TypeArgumentError {
117                argument_idx: idx as TypeParameterIndex,
118                kind: TypeArgumentError::TypeNotFound,
119            }
120            .into(),
121            StatusCode::CONSTRAINT_NOT_SATISFIED => ExecutionErrorKind::TypeArgumentError {
122                argument_idx: idx as TypeParameterIndex,
123                kind: TypeArgumentError::ConstraintNotSatisfied,
124            }
125            .into(),
126            _ => self.convert_linked_vm_error(e, linkage),
127        }
128    }
129
130    pub fn module_definition(
131        &self,
132        module_id: &ModuleId,
133        linkage: &RootedLinkage,
134    ) -> Result<Arc<CompiledModule>, ExecutionError> {
135        let linked_data_store = LinkedDataStore::new(linkage, self.linkable_store);
136        self.vm
137            .get_runtime()
138            .load_module(module_id, &linked_data_store)
139            .map_err(|e| self.convert_linked_vm_error(e, linkage))
140    }
141
142    pub fn fully_annotated_layout(
143        &self,
144        ty: &Type,
145    ) -> Result<annotated_value::MoveTypeLayout, ExecutionError> {
146        let ty = self.load_vm_type_from_adapter_type(None, ty)?;
147        self.vm
148            .get_runtime()
149            .type_to_fully_annotated_layout(&ty)
150            .map_err(|e| self.convert_vm_error(e))
151    }
152
153    pub fn runtime_layout(
154        &self,
155        ty: &Type,
156    ) -> Result<runtime_value::MoveTypeLayout, ExecutionError> {
157        let ty = self.load_vm_type_from_adapter_type(None, ty)?;
158        self.vm
159            .get_runtime()
160            .type_to_type_layout(&ty)
161            .map_err(|e| self.convert_vm_error(e))
162    }
163
164    pub fn load_function(
165        &self,
166        package: ObjectID,
167        module: String,
168        function: String,
169        type_arguments: Vec<Type>,
170        linkage: RootedLinkage,
171    ) -> Result<LoadedFunction, ExecutionError> {
172        let Some(original_id) = linkage.resolved_linkage.resolve_to_original_id(&package) else {
173            invariant_violation!(
174                "Package ID {:?} is not found in linkage generated for that package",
175                package
176            );
177        };
178        let module = to_identifier(module)?;
179        let name = to_identifier(function)?;
180        let storage_id = ModuleId::new(package.into(), module.clone());
181        let runtime_id = ModuleId::new(original_id.into(), module);
182        let mut data_store = LinkedDataStore::new(&linkage, self.linkable_store);
183        let loaded_type_arguments = type_arguments
184            .iter()
185            .enumerate()
186            .map(|(idx, ty)| self.load_vm_type_argument_from_adapter_type(idx, ty))
187            .collect::<Result<Vec<_>, _>>()?;
188        let runtime_signature = self
189            .vm
190            .get_runtime()
191            .load_function(
192                &runtime_id,
193                name.as_ident_str(),
194                &loaded_type_arguments,
195                &mut data_store,
196            )
197            .map_err(|e| {
198                if e.major_status() == StatusCode::FUNCTION_RESOLUTION_FAILURE {
199                    ExecutionError::new_with_source(
200                        ExecutionErrorKind::FunctionNotFound,
201                        format!(
202                            "Could not resolve function '{}' in module {}",
203                            name, &storage_id,
204                        ),
205                    )
206                } else {
207                    self.convert_linked_vm_error(e, &linkage)
208                }
209            })?;
210        let runtime_signature = subst_signature(runtime_signature, &loaded_type_arguments)
211            .map_err(|e| self.convert_linked_vm_error(e, &linkage))?;
212        let parameters = runtime_signature
213            .parameters
214            .into_iter()
215            .map(|ty| self.adapter_type_from_vm_type(&ty))
216            .collect::<Result<Vec<_>, _>>()?;
217        let return_ = runtime_signature
218            .return_
219            .into_iter()
220            .map(|ty| self.adapter_type_from_vm_type(&ty))
221            .collect::<Result<Vec<_>, _>>()?;
222        let signature = LoadedFunctionInstantiation {
223            parameters,
224            return_,
225        };
226        Ok(LoadedFunction {
227            storage_id,
228            runtime_id,
229            name,
230            type_arguments,
231            signature,
232            linkage,
233            instruction_length: runtime_signature.instruction_length,
234            definition_index: runtime_signature.definition_index,
235        })
236    }
237
238    pub fn load_type_input(&self, idx: usize, ty: TypeInput) -> Result<Type, ExecutionError> {
239        let runtime_type = self.load_vm_type_from_type_input(idx, ty)?;
240        self.adapter_type_from_vm_type(&runtime_type)
241    }
242
243    /// We verify that all types in the `StructTag` are defining ID-based types.
244    pub fn load_type_from_struct(&self, tag: &StructTag) -> Result<Type, ExecutionError> {
245        let vm_type =
246            self.load_vm_type_from_type_tag(None, &TypeTag::Struct(Box::new(tag.clone())))?;
247        self.adapter_type_from_vm_type(&vm_type)
248    }
249
250    pub fn type_layout_for_struct(
251        &self,
252        tag: &StructTag,
253    ) -> Result<MoveTypeLayout, ExecutionError> {
254        let ty: Type = self.load_type_from_struct(tag)?;
255        self.runtime_layout(&ty)
256    }
257
258    pub fn gas_coin_type(&self) -> Result<Type, ExecutionError> {
259        get_or_init_ty!(self, gas_coin_type, GasCoin::type_())
260    }
261
262    pub fn upgrade_ticket_type(&self) -> Result<Type, ExecutionError> {
263        get_or_init_ty!(self, upgrade_ticket_type, UpgradeTicket::type_())
264    }
265
266    pub fn upgrade_receipt_type(&self) -> Result<Type, ExecutionError> {
267        get_or_init_ty!(self, upgrade_receipt_type, UpgradeReceipt::type_())
268    }
269
270    pub fn upgrade_cap_type(&self) -> Result<Type, ExecutionError> {
271        get_or_init_ty!(self, upgrade_cap_type, UpgradeCap::type_())
272    }
273
274    pub fn tx_context_type(&self) -> Result<Type, ExecutionError> {
275        get_or_init_ty!(self, tx_context_type, TxContext::type_())
276    }
277
278    pub fn vector_type(&self, element_type: Type) -> Result<Type, ExecutionError> {
279        let abilities = AbilitySet::polymorphic_abilities(
280            AbilitySet::VECTOR,
281            [false],
282            [element_type.abilities()],
283        )
284        .map_err(|e| {
285            ExecutionError::new_with_source(ExecutionErrorKind::VMInvariantViolation, e.to_string())
286        })?;
287        Ok(Type::Vector(Rc::new(L::Vector {
288            abilities,
289            element_type,
290        })))
291    }
292
293    pub fn read_object(&self, id: &ObjectID) -> Result<&Object, ExecutionError> {
294        let Some(obj) = self.state_view.read_object(id) else {
295            // protected by transaction input checker
296            invariant_violation!("Object {:?} does not exist", id);
297        };
298        Ok(obj)
299    }
300
301    /// Takes an adapter Type and returns a VM runtime Type and the linkage for it.
302    pub fn load_vm_type_argument_from_adapter_type(
303        &self,
304        idx: usize,
305        ty: &Type,
306    ) -> Result<vm_runtime_type::Type, ExecutionError> {
307        self.load_vm_type_from_adapter_type(Some(idx), ty)
308    }
309
310    fn load_vm_type_from_adapter_type(
311        &self,
312        type_arg_idx: Option<usize>,
313        ty: &Type,
314    ) -> Result<vm_runtime_type::Type, ExecutionError> {
315        let tag: TypeTag = ty.clone().try_into().map_err(|s| {
316            ExecutionError::new_with_source(ExecutionErrorKind::VMInvariantViolation, s)
317        })?;
318        self.load_vm_type_from_type_tag(type_arg_idx, &tag)
319    }
320
321    /// Take a type tag and returns a VM runtime Type and the linkage for it.
322    fn load_vm_type_from_type_tag(
323        &self,
324        type_arg_idx: Option<usize>,
325        tag: &TypeTag,
326    ) -> Result<vm_runtime_type::Type, ExecutionError> {
327        use vm_runtime_type as VMR;
328
329        fn load_type_tag(
330            env: &Env,
331            type_arg_idx: Option<usize>,
332            tag: &TypeTag,
333        ) -> Result<VMR::Type, ExecutionError> {
334            Ok(match tag {
335                TypeTag::Bool => VMR::Type::Bool,
336                TypeTag::U8 => VMR::Type::U8,
337                TypeTag::U16 => VMR::Type::U16,
338                TypeTag::U32 => VMR::Type::U32,
339                TypeTag::U64 => VMR::Type::U64,
340                TypeTag::U128 => VMR::Type::U128,
341                TypeTag::U256 => VMR::Type::U256,
342                TypeTag::Address => VMR::Type::Address,
343                TypeTag::Signer => VMR::Type::Signer,
344
345                TypeTag::Vector(inner) => {
346                    VMR::Type::Vector(Box::new(load_type_tag(env, type_arg_idx, inner)?))
347                }
348                TypeTag::Struct(tag) => load_struct_tag(env, type_arg_idx, tag)?,
349            })
350        }
351
352        fn load_struct_tag(
353            env: &Env,
354            type_arg_idx: Option<usize>,
355            struct_tag: &StructTag,
356        ) -> Result<vm_runtime_type::Type, ExecutionError> {
357            fn execution_error(
358                env: &Env,
359                type_arg_idx: Option<usize>,
360                e: VMError,
361                linkage: &RootedLinkage,
362            ) -> ExecutionError {
363                if let Some(idx) = type_arg_idx {
364                    env.convert_type_argument_error(idx, e, linkage)
365                } else {
366                    env.convert_linked_vm_error(e, linkage)
367                }
368            }
369
370            fn verification_error(code: StatusCode) -> VMError {
371                PartialVMError::new(code).finish(Location::Undefined)
372            }
373
374            let StructTag {
375                address,
376                module,
377                name,
378                type_params,
379            } = struct_tag;
380
381            let tag_linkage =
382                ResolvedLinkage::type_linkage(&[(*address).into()], env.linkable_store)?;
383            let linkage = RootedLinkage::new(*address, tag_linkage);
384            let linked_store = LinkedDataStore::new(&linkage, env.linkable_store);
385
386            let original_id = linkage
387                .resolved_linkage
388                .resolve_to_original_id(&(*address).into())
389                .ok_or_else(|| {
390                    make_invariant_violation!(
391                        "StructTag {:?} is not found in linkage generated for that struct tag -- this shouldn't happen.",
392                        struct_tag
393                    )
394                })?;
395            let runtime_id = ModuleId::new(*original_id, module.clone());
396
397            let (idx, struct_type) = env
398                .vm
399                .get_runtime()
400                .load_type(&runtime_id, name, &linked_store)
401                .map_err(|e| execution_error(env, type_arg_idx, e, &linkage))?;
402
403            let type_param_constraints = struct_type.type_param_constraints();
404            if type_param_constraints.len() != type_params.len() {
405                return Err(execution_error(
406                    env,
407                    type_arg_idx,
408                    verification_error(StatusCode::NUMBER_OF_TYPE_ARGUMENTS_MISMATCH),
409                    &linkage,
410                ));
411            }
412
413            if type_params.is_empty() {
414                Ok(VMR::Type::Datatype(idx))
415            } else {
416                let loaded_type_params = type_params
417                    .iter()
418                    .map(|type_param| load_type_tag(env, type_arg_idx, type_param))
419                    .collect::<Result<Vec<_>, _>>()?;
420
421                // Verify that the type parameter constraints on the struct are met
422                for (constraint, param) in type_param_constraints.zip(&loaded_type_params) {
423                    let abilities = env
424                        .vm
425                        .get_runtime()
426                        .get_type_abilities(param)
427                        .map_err(|e| execution_error(env, type_arg_idx, e, &linkage))?;
428                    if !constraint.is_subset(abilities) {
429                        return Err(execution_error(
430                            env,
431                            type_arg_idx,
432                            verification_error(StatusCode::CONSTRAINT_NOT_SATISFIED),
433                            &linkage,
434                        ));
435                    }
436                }
437
438                Ok(VMR::Type::DatatypeInstantiation(Box::new((
439                    idx,
440                    loaded_type_params,
441                ))))
442            }
443        }
444
445        load_type_tag(self, type_arg_idx, tag)
446    }
447
448    /// Converts a VM runtime Type to an adapter Type.
449    fn adapter_type_from_vm_type(
450        &self,
451        vm_type: &vm_runtime_type::Type,
452    ) -> Result<Type, ExecutionError> {
453        use vm_runtime_type as VRT;
454
455        Ok(match vm_type {
456            VRT::Type::Bool => Type::Bool,
457            VRT::Type::U8 => Type::U8,
458            VRT::Type::U16 => Type::U16,
459            VRT::Type::U32 => Type::U32,
460            VRT::Type::U64 => Type::U64,
461            VRT::Type::U128 => Type::U128,
462            VRT::Type::U256 => Type::U256,
463            VRT::Type::Address => Type::Address,
464            VRT::Type::Signer => Type::Signer,
465
466            VRT::Type::Reference(ref_ty) => {
467                let inner_ty = self.adapter_type_from_vm_type(ref_ty)?;
468                Type::Reference(false, Rc::new(inner_ty))
469            }
470            VRT::Type::MutableReference(ref_ty) => {
471                let inner_ty = self.adapter_type_from_vm_type(ref_ty)?;
472                Type::Reference(true, Rc::new(inner_ty))
473            }
474
475            VRT::Type::Vector(inner) => {
476                let element_type = self.adapter_type_from_vm_type(inner)?;
477                let abilities = self
478                    .vm
479                    .get_runtime()
480                    .get_type_abilities(vm_type)
481                    .map_err(|e| self.convert_vm_error(e))?;
482                let vector_ty = Vector {
483                    abilities,
484                    element_type,
485                };
486                Type::Vector(Rc::new(vector_ty))
487            }
488            VRT::Type::Datatype(cached_type_index) => {
489                let runtime = self.vm.get_runtime();
490                let Some(cached_info) = runtime.get_type(*cached_type_index) else {
491                    invariant_violation!(
492                        "Unable to find cached type info for {:?}. This should not happen as we have \
493                         a loaded VM type in-hand.",
494                        vm_type
495                    )
496                };
497                let datatype = Datatype {
498                    abilities: cached_info.abilities,
499                    module: cached_info.defining_id.clone(),
500                    name: cached_info.name.clone(),
501                    type_arguments: vec![],
502                };
503                Type::Datatype(Rc::new(datatype))
504            }
505            ty @ VRT::Type::DatatypeInstantiation(inst) => {
506                let (cached_type_index, type_arguments) = &**inst;
507                let runtime = self.vm.get_runtime();
508                let Some(cached_info) = runtime.get_type(*cached_type_index) else {
509                    invariant_violation!(
510                        "Unable to find cached type info for {:?}. This should not happen as we have \
511                         a loaded VM type in-hand.",
512                        vm_type
513                    )
514                };
515
516                let abilities = runtime
517                    .get_type_abilities(ty)
518                    .map_err(|e| self.convert_vm_error(e))?;
519                let module = cached_info.defining_id.clone();
520                let name = cached_info.name.clone();
521                let type_arguments = type_arguments
522                    .iter()
523                    .map(|t| self.adapter_type_from_vm_type(t))
524                    .collect::<Result<Vec<_>, _>>()?;
525
526                Type::Datatype(Rc::new(Datatype {
527                    abilities,
528                    module,
529                    name,
530                    type_arguments,
531                }))
532            }
533
534            VRT::Type::TyParam(_) => {
535                invariant_violation!(
536                    "Unexpected type parameter in VM type: {:?}. This should not happen as we should \
537                     have resolved all type parameters before this point.",
538                    vm_type
539                );
540            }
541        })
542    }
543
544    /// Load a `TypeInput` into a VM runtime `Type` and its `Linkage`. Loading into the VM ensures
545    /// that any adapter type or type tag that results from this is properly output with defining
546    /// IDs.
547    fn load_vm_type_from_type_input(
548        &self,
549        type_arg_idx: usize,
550        ty: TypeInput,
551    ) -> Result<vm_runtime_type::Type, ExecutionError> {
552        fn to_type_tag_internal(
553            env: &Env,
554            type_arg_idx: usize,
555            ty: TypeInput,
556        ) -> Result<TypeTag, ExecutionError> {
557            Ok(match ty {
558                TypeInput::Bool => TypeTag::Bool,
559                TypeInput::U8 => TypeTag::U8,
560                TypeInput::U16 => TypeTag::U16,
561                TypeInput::U32 => TypeTag::U32,
562                TypeInput::U64 => TypeTag::U64,
563                TypeInput::U128 => TypeTag::U128,
564                TypeInput::U256 => TypeTag::U256,
565                TypeInput::Address => TypeTag::Address,
566                TypeInput::Signer => TypeTag::Signer,
567                TypeInput::Vector(type_input) => {
568                    let inner = to_type_tag_internal(env, type_arg_idx, *type_input)?;
569                    TypeTag::Vector(Box::new(inner))
570                }
571                TypeInput::Struct(struct_input) => {
572                    let StructInput {
573                        address,
574                        module,
575                        name,
576                        type_params,
577                    } = *struct_input;
578
579                    let pkg = env
580                        .linkable_store
581                        .get_package(&address.into())
582                        .ok()
583                        .flatten()
584                        .ok_or_else(|| {
585                            ExecutionError::from_kind(ExecutionErrorKind::TypeArgumentError {
586                                argument_idx: type_arg_idx as u16,
587                                kind: TypeArgumentError::TypeNotFound,
588                            })
589                        })?;
590                    let Some(resolved_address) = pkg
591                        .type_origin_map()
592                        .get(&(module.clone(), name.clone()))
593                        .cloned()
594                    else {
595                        return Err(ExecutionError::from_kind(
596                            ExecutionErrorKind::TypeArgumentError {
597                                argument_idx: type_arg_idx as u16,
598                                kind: TypeArgumentError::TypeNotFound,
599                            },
600                        ));
601                    };
602
603                    let module = to_identifier(module)?;
604                    let name = to_identifier(name)?;
605                    let tys = type_params
606                        .into_iter()
607                        .map(|tp| to_type_tag_internal(env, type_arg_idx, tp))
608                        .collect::<Result<Vec<_>, _>>()?;
609                    TypeTag::Struct(Box::new(StructTag {
610                        address: *resolved_address,
611                        module,
612                        name,
613                        type_params: tys,
614                    }))
615                }
616            })
617        }
618        let tag = to_type_tag_internal(self, type_arg_idx, ty)?;
619        self.load_vm_type_from_type_tag(Some(type_arg_idx), &tag)
620    }
621}
622
623fn to_identifier(name: String) -> Result<Identifier, ExecutionError> {
624    Identifier::new(name).map_err(|e| {
625        ExecutionError::new_with_source(ExecutionErrorKind::VMInvariantViolation, e.to_string())
626    })
627}
628
629fn convert_vm_error(
630    error: VMError,
631    vm: &MoveVM,
632    store: &dyn PackageStore,
633    linkage: Option<&RootedLinkage>,
634) -> ExecutionError {
635    use crate::error::convert_vm_error_impl;
636    convert_vm_error_impl(
637        error,
638        &|id| {
639            debug_assert!(
640                linkage.is_some(),
641                "Linkage should be set anywhere where runtime errors may occur in order to resolve abort locations to package IDs"
642            );
643            linkage
644                .and_then(|linkage| LinkedDataStore::new(linkage, store).relocate(id).ok())
645                .unwrap_or_else(|| id.clone())
646        },
647        // NB: the `id` here is the original ID (and hence _not_ relocated).
648        &|id, function| {
649            debug_assert!(
650                linkage.is_some(),
651                "Linkage should be set anywhere where runtime errors may occur in order to resolve abort locations to package IDs"
652            );
653            linkage.and_then(|linkage| {
654                let state_view = LinkedDataStore::new(linkage, store);
655                vm.load_module(id, state_view).ok().map(|module| {
656                    let fdef = module.function_def_at(function);
657                    let fhandle = module.function_handle_at(fdef.function);
658                    module.identifier_at(fhandle.name).to_string()
659                })
660            })
661        },
662    )
663}