Coverage Report

Created: 2026-06-07 07:42

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/wasm-tools/crates/wit-component/src/linking/metadata.rs
Line
Count
Source
1
//! Support for parsing and analyzing [dynamic
2
//! library](https://github.com/WebAssembly/tool-conventions/blob/main/DynamicLinking.md) modules.
3
4
use {
5
    anyhow::{Context, Error, Result, bail},
6
    std::{
7
        collections::{BTreeSet, HashMap, HashSet},
8
        fmt,
9
    },
10
    wasmparser::{
11
        Dylink0Subsection, ExternalKind, FuncType, KnownCustom, MemInfo, Parser, Payload, RefType,
12
        SymbolFlags, TableType, TagKind, TagType, TypeRef, ValType,
13
    },
14
};
15
16
/// Represents a core Wasm value type (not including V128 or reference types, which are not yet supported)
17
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
18
pub enum ValueType {
19
    I32,
20
    I64,
21
    F32,
22
    F64,
23
}
24
25
impl TryFrom<ValType> for ValueType {
26
    type Error = Error;
27
28
0
    fn try_from(value: ValType) -> Result<Self> {
29
0
        Ok(match value {
30
0
            ValType::I32 => Self::I32,
31
0
            ValType::I64 => Self::I64,
32
0
            ValType::F32 => Self::F32,
33
0
            ValType::F64 => Self::F64,
34
0
            _ => bail!("{value:?} not yet supported"),
35
        })
36
0
    }
37
}
38
39
impl From<ValueType> for wasm_encoder::ValType {
40
0
    fn from(value: ValueType) -> Self {
41
0
        match value {
42
0
            ValueType::I32 => Self::I32,
43
0
            ValueType::I64 => Self::I64,
44
0
            ValueType::F32 => Self::F32,
45
0
            ValueType::F64 => Self::F64,
46
        }
47
0
    }
48
}
49
50
/// Represents a core Wasm function type
51
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
52
pub struct FunctionType {
53
    pub parameters: Vec<ValueType>,
54
    pub results: Vec<ValueType>,
55
}
56
57
impl fmt::Display for FunctionType {
58
0
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
59
0
        write!(f, "{:?} -> {:?}", self.parameters, self.results)
60
0
    }
61
}
62
63
impl TryFrom<&FuncType> for FunctionType {
64
    type Error = Error;
65
66
0
    fn try_from(value: &FuncType) -> Result<Self> {
67
        Ok(Self {
68
0
            parameters: value
69
0
                .params()
70
0
                .iter()
71
0
                .map(|&v| ValueType::try_from(v))
72
0
                .collect::<Result<_>>()?,
73
0
            results: value
74
0
                .results()
75
0
                .iter()
76
0
                .map(|&v| ValueType::try_from(v))
77
0
                .collect::<Result<_>>()?,
78
        })
79
0
    }
80
}
81
82
/// Represents a core Wasm global variable type
83
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
84
pub struct GlobalType {
85
    pub ty: ValueType,
86
    pub mutable: bool,
87
    pub shared: bool,
88
}
89
90
impl fmt::Display for GlobalType {
91
0
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
92
0
        if self.mutable {
93
0
            write!(f, "mut ")?;
94
0
        }
95
0
        write!(f, "{:?}", self.ty)
96
0
    }
97
}
98
99
/// Represents a core Wasm export or import type
100
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
101
pub enum Type {
102
    Function(FunctionType),
103
    Global(GlobalType),
104
    Tag(FunctionType),
105
}
106
107
impl fmt::Display for Type {
108
0
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
109
0
        match self {
110
0
            Self::Function(ty) => write!(f, "function {ty}"),
111
0
            Self::Global(ty) => write!(f, "global {ty}"),
112
0
            Self::Tag(ty) => write!(f, "tag {ty}"),
113
        }
114
0
    }
115
}
116
117
impl From<&Type> for wasm_encoder::ExportKind {
118
0
    fn from(value: &Type) -> Self {
119
0
        match value {
120
0
            Type::Function(_) => wasm_encoder::ExportKind::Func,
121
0
            Type::Global(_) => wasm_encoder::ExportKind::Global,
122
0
            Type::Tag(_) => wasm_encoder::ExportKind::Tag,
123
        }
124
0
    }
125
}
126
127
/// Represents a core Wasm import
128
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
129
pub struct Import<'a> {
130
    pub module: &'a str,
131
    pub name: &'a str,
132
    pub ty: Type,
133
    pub flags: SymbolFlags,
134
}
135
136
/// Represents a core Wasm export
137
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
138
pub struct ExportKey<'a> {
139
    pub name: &'a str,
140
    pub ty: Type,
141
}
142
143
impl<'a> fmt::Display for ExportKey<'a> {
144
0
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
145
0
        write!(f, "{} ({})", self.name, self.ty)
146
0
    }
147
}
148
149
/// Represents a core Wasm export, including dylink.0 flags
150
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
151
pub struct Export<'a> {
152
    pub key: ExportKey<'a>,
153
    pub flags: SymbolFlags,
154
}
155
156
/// Metadata extracted from a dynamic library module
157
#[derive(Debug)]
158
pub struct Metadata<'a> {
159
    /// The name of the module
160
    ///
161
    /// This is currently not part of the file itself and must be provided separately, but the plan is to add
162
    /// something like a `WASM_DYLINK_SO_NAME` field to the dynamic linking tool convention so we can parse it
163
    /// along with everything else.
164
    pub name: &'a str,
165
166
    /// Whether this module should be resolvable via `dlopen`
167
    pub dl_openable: bool,
168
169
    /// The `WASM_DYLINK_MEM_INFO` value (or all zeros if not found)
170
    pub mem_info: MemInfo,
171
172
    /// The `WASM_DYLINK_NEEDED` values, if any
173
    pub needed_libs: Vec<&'a str>,
174
175
    /// The `WASM_DYLINK_RUNTIME_PATH` values, if any
176
    pub runtime_path: Vec<&'a str>,
177
178
    /// Whether this module exports `__wasm_apply_data_relocs`
179
    pub has_data_relocs: bool,
180
181
    /// Whether this module exports `__wasm_call_ctors`
182
    pub has_ctors: bool,
183
184
    /// Whether this module exports `_initialize`
185
    pub has_initialize: bool,
186
187
    /// Whether this module exports `_start`
188
    pub has_wasi_start: bool,
189
190
    /// Whether this module exports `__wasm_set_libraries`
191
    pub has_set_libraries: bool,
192
193
    /// Whether this module includes any `component-type*` custom sections which include exports
194
    pub has_component_exports: bool,
195
196
    /// Whether this module imports `__asyncify_state` or `__asyncify_data`, indicating that it is
197
    /// asyncified with `--pass-arg=asyncify-relocatable` option.
198
    pub is_asyncified: bool,
199
200
    /// The functions imported from the `env` module, if any
201
    pub env_imports: BTreeSet<(&'a str, (FunctionType, SymbolFlags))>,
202
203
    /// The memory addresses imported from `GOT.mem`, if any
204
    pub memory_address_imports: BTreeSet<&'a str>,
205
206
    /// The table addresses imported from `GOT.func`, if any
207
    pub table_address_imports: BTreeSet<&'a str>,
208
209
    /// Imported exception tags
210
    pub tag_imports: BTreeSet<(&'a str, FunctionType)>,
211
212
    /// The symbols exported by this module, if any
213
    pub exports: BTreeSet<Export<'a>>,
214
215
    /// The symbols imported by this module (and not accounted for in the above fields), if any
216
    pub imports: BTreeSet<Import<'a>>,
217
}
218
219
impl<'a> Metadata<'a> {
220
    /// Parse the specified module and extract its metadata.
221
0
    pub fn try_new(
222
0
        name: &'a str,
223
0
        dl_openable: bool,
224
0
        module: &'a [u8],
225
0
        adapter_names: &HashSet<&str>,
226
0
    ) -> Result<Self> {
227
0
        let bindgen = crate::metadata::decode(module)?.1;
228
0
        let has_component_exports = !bindgen.resolve.worlds[bindgen.world].exports.is_empty();
229
230
0
        let mut result = Self {
231
0
            name,
232
0
            dl_openable,
233
0
            mem_info: MemInfo {
234
0
                memory_size: 0,
235
0
                memory_alignment: 1,
236
0
                table_size: 0,
237
0
                table_alignment: 1,
238
0
            },
239
0
            needed_libs: Vec::new(),
240
0
            runtime_path: Vec::new(),
241
0
            has_data_relocs: false,
242
0
            has_ctors: false,
243
0
            has_initialize: false,
244
0
            has_wasi_start: false,
245
0
            has_set_libraries: false,
246
0
            has_component_exports,
247
0
            is_asyncified: false,
248
0
            env_imports: BTreeSet::new(),
249
0
            memory_address_imports: BTreeSet::new(),
250
0
            table_address_imports: BTreeSet::new(),
251
0
            exports: BTreeSet::new(),
252
0
            imports: BTreeSet::new(),
253
0
            tag_imports: BTreeSet::new(),
254
0
        };
255
0
        let mut types = Vec::new();
256
0
        let mut function_types = Vec::new();
257
0
        let mut global_types = Vec::new();
258
0
        let mut tag_types = Vec::new();
259
0
        let mut import_info = HashMap::new();
260
0
        let mut export_info = HashMap::new();
261
262
0
        for payload in Parser::new(0).parse_all(module) {
263
0
            match payload? {
264
0
                Payload::CustomSection(section) => {
265
0
                    if let KnownCustom::Dylink0(reader) = section.as_known() {
266
0
                        for subsection in reader {
267
0
                            match subsection.context("failed to parse `dylink.0` subsection")? {
268
0
                                Dylink0Subsection::MemInfo(info) => result.mem_info = info,
269
0
                                Dylink0Subsection::Needed(needed) => {
270
0
                                    result.needed_libs = needed.clone()
271
                                }
272
0
                                Dylink0Subsection::ExportInfo(info) => {
273
0
                                    export_info
274
0
                                        .extend(info.iter().map(|info| (info.name, info.flags)));
275
                                }
276
0
                                Dylink0Subsection::ImportInfo(info) => {
277
0
                                    import_info.extend(
278
0
                                        info.iter()
279
0
                                            .map(|info| ((info.module, info.field), info.flags)),
280
                                    );
281
                                }
282
0
                                Dylink0Subsection::RuntimePath(runtime_path) => {
283
0
                                    result.runtime_path.extend(runtime_path.iter());
284
0
                                }
285
0
                                Dylink0Subsection::Unknown { ty, .. } => {
286
0
                                    bail!("unrecognized `dylink.0` subsection: {ty}")
287
                                }
288
                            }
289
                        }
290
0
                    }
291
                }
292
293
0
                Payload::TypeSection(reader) => {
294
0
                    types = reader
295
0
                        .into_iter_err_on_gc_types()
296
0
                        .collect::<Result<Vec<_>, _>>()?;
297
                }
298
299
0
                Payload::ImportSection(reader) => {
300
0
                    for import in reader.into_imports() {
301
0
                        let import = import?;
302
303
0
                        match import.ty {
304
0
                            TypeRef::Func(ty) => function_types.push(usize::try_from(ty).unwrap()),
305
0
                            TypeRef::Global(ty) => global_types.push(ty),
306
0
                            TypeRef::Tag(ty) => tag_types.push(ty),
307
0
                            _ => (),
308
                        }
309
310
0
                        let type_error = || {
311
0
                            bail!(
312
                                "unexpected type for {}:{}: {:?}",
313
                                import.module,
314
                                import.name,
315
                                import.ty
316
                            )
317
0
                        };
318
319
0
                        match (import.module, import.name) {
320
0
                            ("env", "memory") => {
321
0
                                if !matches!(import.ty, TypeRef::Memory(_)) {
322
0
                                    return type_error();
323
0
                                }
324
                            }
325
0
                            ("env", "__asyncify_data" | "__asyncify_state") => {
326
0
                                result.is_asyncified = true;
327
0
                                if !matches!(
328
0
                                    import.ty,
329
                                    TypeRef::Global(wasmparser::GlobalType {
330
                                        content_type: ValType::I32,
331
                                        ..
332
                                    })
333
                                ) {
334
0
                                    return type_error();
335
0
                                }
336
                            }
337
0
                            ("env", "__memory_base" | "__table_base" | "__stack_pointer") => {
338
0
                                if !matches!(
339
0
                                    import.ty,
340
                                    TypeRef::Global(wasmparser::GlobalType {
341
                                        content_type: ValType::I32,
342
                                        ..
343
                                    })
344
                                ) {
345
0
                                    return type_error();
346
0
                                }
347
                            }
348
0
                            ("env", "__indirect_function_table") => {
349
                                if let TypeRef::Table(TableType {
350
0
                                    element_type,
351
                                    maximum: None,
352
                                    ..
353
0
                                }) = import.ty
354
                                {
355
0
                                    if element_type != RefType::FUNCREF {
356
0
                                        return type_error();
357
0
                                    }
358
                                } else {
359
0
                                    return type_error();
360
                                }
361
                            }
362
0
                            ("env", name) => match import.ty {
363
0
                                TypeRef::Func(ty) => {
364
0
                                    result.env_imports.insert((
365
0
                                        name,
366
                                        (
367
0
                                            FunctionType::try_from(
368
0
                                                &types[usize::try_from(ty).unwrap()],
369
0
                                            )?,
370
0
                                            import_info
371
0
                                                .get(&("env", name))
372
0
                                                .copied()
373
0
                                                .unwrap_or_default(),
374
                                        ),
375
                                    ));
376
                                }
377
                                TypeRef::Tag(TagType {
378
                                    kind: TagKind::Exception,
379
0
                                    func_type_idx,
380
                                }) => {
381
0
                                    result.tag_imports.insert((
382
0
                                        name,
383
0
                                        FunctionType::try_from(
384
0
                                            &types[usize::try_from(func_type_idx).unwrap()],
385
0
                                        )?,
386
                                    ));
387
                                }
388
0
                                _ => return type_error(),
389
                            },
390
0
                            ("GOT.mem", name) => {
391
                                if let TypeRef::Global(wasmparser::GlobalType {
392
                                    content_type: ValType::I32,
393
                                    ..
394
0
                                }) = import.ty
395
                                {
396
0
                                    match name {
397
0
                                        "__heap_base" | "__heap_end" | "__stack_high"
398
0
                                        | "__stack_low" => (),
399
0
                                        _ => {
400
0
                                            result.memory_address_imports.insert(name);
401
0
                                        }
402
                                    }
403
                                } else {
404
0
                                    return type_error();
405
                                }
406
                            }
407
0
                            ("GOT.func", name) => {
408
                                if let TypeRef::Global(wasmparser::GlobalType {
409
                                    content_type: ValType::I32,
410
                                    ..
411
0
                                }) = import.ty
412
0
                                {
413
0
                                    result.table_address_imports.insert(name);
414
0
                                } else {
415
0
                                    return type_error();
416
                                }
417
                            }
418
0
                            (module, name) if adapter_names.contains(module) => {
419
0
                                let ty = match import.ty {
420
                                    TypeRef::Global(wasmparser::GlobalType {
421
0
                                        content_type,
422
0
                                        mutable,
423
0
                                        shared,
424
                                    }) => Type::Global(GlobalType {
425
0
                                        ty: content_type.try_into()?,
426
0
                                        mutable,
427
0
                                        shared,
428
                                    }),
429
0
                                    TypeRef::Func(ty) => Type::Function(FunctionType::try_from(
430
0
                                        &types[usize::try_from(ty).unwrap()],
431
0
                                    )?),
432
0
                                    ty => {
433
0
                                        bail!("unsupported import kind for {module}.{name}: {ty:?}",)
434
                                    }
435
                                };
436
0
                                let flags = import_info
437
0
                                    .get(&(module, name))
438
0
                                    .copied()
439
0
                                    .unwrap_or_default();
440
0
                                result.imports.insert(Import {
441
0
                                    module,
442
0
                                    name,
443
0
                                    ty,
444
0
                                    flags,
445
0
                                });
446
                            }
447
                            _ => {
448
0
                                if !matches!(import.ty, TypeRef::Func(_) | TypeRef::Global(_)) {
449
0
                                    return type_error();
450
0
                                }
451
                            }
452
                        }
453
                    }
454
                }
455
456
0
                Payload::FunctionSection(reader) => {
457
0
                    for function in reader {
458
0
                        function_types.push(usize::try_from(function?).unwrap());
459
                    }
460
                }
461
462
0
                Payload::GlobalSection(reader) => {
463
0
                    for global in reader {
464
0
                        global_types.push(global?.ty);
465
                    }
466
                }
467
468
0
                Payload::TagSection(reader) => {
469
0
                    for tag in reader {
470
0
                        tag_types.push(tag?);
471
                    }
472
                }
473
474
0
                Payload::ExportSection(reader) => {
475
0
                    for export in reader {
476
0
                        let export = export?;
477
478
0
                        match export.name {
479
0
                            "__wasm_apply_data_relocs" => result.has_data_relocs = true,
480
0
                            "__wasm_call_ctors" => result.has_ctors = true,
481
0
                            "_initialize" => result.has_initialize = true,
482
0
                            "_start" => result.has_wasi_start = true,
483
0
                            "__wasm_set_libraries" => result.has_set_libraries = true,
484
                            _ => {
485
0
                                let ty = match export.kind {
486
0
                                    ExternalKind::Func => Type::Function(FunctionType::try_from(
487
0
                                        &types[function_types
488
0
                                            [usize::try_from(export.index).unwrap()]],
489
0
                                    )?),
490
                                    ExternalKind::Global => {
491
0
                                        let ty =
492
0
                                            global_types[usize::try_from(export.index).unwrap()];
493
                                        Type::Global(GlobalType {
494
0
                                            ty: ValueType::try_from(ty.content_type)?,
495
0
                                            mutable: ty.mutable,
496
0
                                            shared: ty.shared,
497
                                        })
498
                                    }
499
0
                                    ExternalKind::Tag => Type::Tag(FunctionType::try_from(
500
0
                                        &types[usize::try_from(
501
0
                                            tag_types[usize::try_from(export.index).unwrap()]
502
0
                                                .func_type_idx,
503
0
                                        )
504
0
                                        .unwrap()],
505
0
                                    )?),
506
0
                                    kind => {
507
0
                                        bail!(
508
                                            "unsupported export kind for {}: {kind:?}",
509
                                            export.name
510
                                        )
511
                                    }
512
                                };
513
0
                                let flags =
514
0
                                    export_info.get(&export.name).copied().unwrap_or_default();
515
0
                                result.exports.insert(Export {
516
0
                                    key: ExportKey {
517
0
                                        name: export.name,
518
0
                                        ty,
519
0
                                    },
520
0
                                    flags,
521
0
                                });
522
                            }
523
                        }
524
                    }
525
                }
526
527
0
                _ => {}
528
            }
529
        }
530
531
0
        Ok(result)
532
0
    }
533
}