Skip to content

Commit 314b614

Browse files
authored
Fix panic processing invalid WIT (#1991)
* Fix panic processing invalid WIT When an error was being generated part of the error message generation was producing a panic by accessing some data within `Resolve` before it was updated. The fix here is to move the data-updating before the error message generation to resolve the panic. * Reformat some long lines Let `rustfmt` be able to wrap the lines * Fix `cargo test -p wit-parser` in isolation * Thread `Span` information for stability errors instead
1 parent 6a44095 commit 314b614

File tree

6 files changed

+85
-30
lines changed

6 files changed

+85
-30
lines changed

crates/wit-parser/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ serde_derive = { workspace = true, optional = true }
2727
serde_json = { workspace = true, optional = true }
2828
unicode-xid = "0.2.2"
2929
wasmparser = { workspace = true, optional = true, features = ['std', 'validate', 'component-model', 'features'] }
30-
wat = { workspace = true, optional = true }
30+
wat = { workspace = true, optional = true, features = ['component-model'] }
3131

3232
[features]
3333
default = ['serde', 'decoding']

crates/wit-parser/src/resolve.rs

Lines changed: 59 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use std::fmt;
55
use std::mem;
66
use std::path::{Path, PathBuf};
77

8-
use anyhow::{anyhow, bail, ensure, Context, Result};
8+
use anyhow::{anyhow, bail, Context, Result};
99
use id_arena::{Arena, Id};
1010
use indexmap::{IndexMap, IndexSet};
1111
use semver::Version;
@@ -1778,30 +1778,67 @@ package {name} is defined in two different locations:\n\
17781778
}
17791779
}
17801780

1781-
fn include_stability(&self, stability: &Stability, pkg_id: &PackageId) -> Result<bool> {
1781+
/// Returns whether the `stability` annotation contained within `pkg_id`
1782+
/// should be included or not.
1783+
///
1784+
/// The `span` provided here is an optional span pointing to the item that
1785+
/// is annotated with `stability`.
1786+
///
1787+
/// Returns `Ok(true)` if the item is included, or `Ok(false)` if the item
1788+
/// is not.
1789+
///
1790+
/// # Errors
1791+
///
1792+
/// Returns an error if the `pkg_id` isn't annotated with sufficient version
1793+
/// information to have a `stability` annotation. For example if `pkg_id`
1794+
/// has no version listed then an error will be returned if `stability`
1795+
/// mentions a version.
1796+
fn include_stability(
1797+
&self,
1798+
stability: &Stability,
1799+
pkg_id: &PackageId,
1800+
span: Option<Span>,
1801+
) -> Result<bool> {
1802+
let err = |msg: String| match span {
1803+
Some(span) => Error::new(span, msg).into(),
1804+
None => anyhow::Error::msg(msg),
1805+
};
17821806
Ok(match stability {
17831807
Stability::Unknown => true,
1784-
// NOTE: deprecations are intentionally omitted -- an existing `@since` takes precedence over `@deprecated`
1808+
// NOTE: deprecations are intentionally omitted -- an existing
1809+
// `@since` takes precedence over `@deprecated`
17851810
Stability::Stable { since, .. } => {
17861811
let Some(p) = self.packages.get(*pkg_id) else {
1787-
// We can't check much without a package (possibly dealing with an item in an `UnresolvedPackage`),
1788-
// @since version & deprecations can't be checked because there's no package version to compare to.
1812+
// We can't check much without a package (possibly dealing
1813+
// with an item in an `UnresolvedPackage`), @since version &
1814+
// deprecations can't be checked because there's no package
1815+
// version to compare to.
17891816
//
1790-
// Feature requirements on stabilized features are ignored in resolved packages, so we do the same here.
1817+
// Feature requirements on stabilized features are ignored
1818+
// in resolved packages, so we do the same here.
17911819
return Ok(true);
17921820
};
17931821

1794-
// Use of feature gating with version specifiers inside a package that is not versioned is not allowed
1795-
let package_version = p.name.version.as_ref().with_context(|| format!("package [{}] contains a feature gate with a version specifier, so it must have a version", p.name))?;
1822+
// Use of feature gating with version specifiers inside a
1823+
// package that is not versioned is not allowed
1824+
let package_version = p.name.version.as_ref().ok_or_else(|| {
1825+
err(format!(
1826+
"package [{}] contains a feature gate with a version \
1827+
specifier, so it must have a version",
1828+
p.name
1829+
))
1830+
})?;
17961831

17971832
// If the version on the feature gate is:
17981833
// - released, then we can include it
17991834
// - unreleased, then we must check the feature (if present)
1800-
ensure!(
1801-
since <= package_version,
1802-
"feature gate cannot reference unreleased version {since} of package [{}] (current version {package_version})",
1803-
p.name
1804-
);
1835+
if since > package_version {
1836+
return Err(err(format!(
1837+
"feature gate cannot reference unreleased version \
1838+
{since} of package [{}] (current version {package_version})",
1839+
p.name
1840+
)));
1841+
}
18051842

18061843
true
18071844
}
@@ -2616,7 +2653,7 @@ impl Remap {
26162653
.skip(foreign_types)
26172654
{
26182655
if !resolve
2619-
.include_stability(&ty.stability, &pkgid)
2656+
.include_stability(&ty.stability, &pkgid, Some(*span))
26202657
.with_context(|| {
26212658
format!(
26222659
"failed to process feature gate for type [{}] in package [{}]",
@@ -2666,7 +2703,7 @@ impl Remap {
26662703
.skip(foreign_interfaces)
26672704
{
26682705
if !resolve
2669-
.include_stability(&iface.stability, &pkgid)
2706+
.include_stability(&iface.stability, &pkgid, Some(span.span))
26702707
.with_context(|| {
26712708
format!(
26722709
"failed to process feature gate for interface [{}] in package [{}]",
@@ -2724,7 +2761,7 @@ impl Remap {
27242761
.skip(foreign_worlds)
27252762
{
27262763
if !resolve
2727-
.include_stability(&world.stability, &pkgid)
2764+
.include_stability(&world.stability, &pkgid, Some(span.span))
27282765
.with_context(|| {
27292766
format!(
27302767
"failed to process feature gate for world [{}] in package [{}]",
@@ -3119,8 +3156,9 @@ impl Remap {
31193156
assert_eq!(iface.functions.len(), spans.funcs.len());
31203157
}
31213158
for (i, (func_name, func)) in iface.functions.iter_mut().enumerate() {
3159+
let span = spans.map(|s| s.funcs[i]);
31223160
if !resolve
3123-
.include_stability(&func.stability, iface_pkg_id)
3161+
.include_stability(&func.stability, iface_pkg_id, span)
31243162
.with_context(|| {
31253163
format!(
31263164
"failed to process feature gate for function [{func_name}] in package [{}]",
@@ -3130,15 +3168,14 @@ impl Remap {
31303168
{
31313169
continue;
31323170
}
3133-
let span = spans.map(|s| s.funcs[i]);
31343171
self.update_function(resolve, func, span)
31353172
.with_context(|| format!("failed to update function `{}`", func.name))?;
31363173
}
31373174

31383175
// Filter out all of the existing functions in interface which fail the
31393176
// `include_stability()` check, as they shouldn't be available.
31403177
for (name, func) in mem::take(&mut iface.functions) {
3141-
if resolve.include_stability(&func.stability, iface_pkg_id)? {
3178+
if resolve.include_stability(&func.stability, iface_pkg_id, None)? {
31423179
iface.functions.insert(name, func);
31433180
}
31443181
}
@@ -3216,14 +3253,8 @@ impl Remap {
32163253
}
32173254
let stability = item.stability(resolve);
32183255
if !resolve
3219-
.include_stability(stability, pkg_id)
3220-
.with_context(|| {
3221-
format!(
3222-
"failed to process imported world item type [{}] in package [{}]",
3223-
resolve.name_world_key(&name),
3224-
resolve.packages[*pkg_id].name,
3225-
)
3226-
})?
3256+
.include_stability(stability, pkg_id, Some(*span))
3257+
.with_context(|| format!("failed to process world item in `{}`", world.name))?
32273258
{
32283259
continue;
32293260
}
@@ -3260,7 +3291,7 @@ impl Remap {
32603291
.zip(&include_names)
32613292
{
32623293
if !resolve
3263-
.include_stability(&stability, pkg_id)
3294+
.include_stability(&stability, pkg_id, Some(*span))
32643295
.with_context(|| {
32653296
format!(
32663297
"failed to process feature gate for included world [{}] in package [{}]",

crates/wit-parser/tests/ui/streams-and-futures.wit.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,4 +206,4 @@
206206
"worlds": {}
207207
}
208208
]
209-
}
209+
}

tests/cli/bad-stability.wit

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// FAIL: component wit %
2+
3+
package a:b;
4+
5+
world c {
6+
@since(version = 0.2.0)
7+
import d:e/f-g-h@0.2.3;
8+
}
9+
10+
package d:e@0.2.3 {
11+
interface f-g-h {}
12+
}

tests/cli/bad-stability.wit.stderr

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
error: failed to process world item in `c`
2+
3+
Caused by:
4+
0: package [a:b] contains a feature gate with a version specifier, so it must have a version
5+
--> tests/cli/bad-stability.wit:7:14
6+
|
7+
7 | import d:e/f-g-h@0.2.3;
8+
| ^----

tests/cli/since-on-future-package.wit.stderr

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,7 @@ error: failed to process feature gate for function [b] in package [test:invalid@
22

33
Caused by:
44
0: feature gate cannot reference unreleased version 0.1.1 of package [test:invalid@0.1.0] (current version 0.1.0)
5+
--> tests/cli/since-on-future-package.wit:9:3
6+
|
7+
9 | b: func(s: string) -> string;
8+
| ^

0 commit comments

Comments
 (0)