Skip to content

Fix integer underflow in all_mutually_exclusive for empty subschemas - #1028

Open
danieleades wants to merge 1 commit into
oxidecomputer:mainfrom
danieleades:fix/empty-subschemas
Open

Fix integer underflow in all_mutually_exclusive for empty subschemas#1028
danieleades wants to merge 1 commit into
oxidecomputer:mainfrom
danieleades:fix/empty-subschemas

Conversation

@danieleades

Copy link
Copy Markdown
Contributor

Problem

all_mutually_exclusive in typify-impl/src/util.rs computes (0..len - 1) over the subschema list. When the list is empty, len - 1 underflows: this panics in debug builds and, in release builds, wraps to usize::MAX so the pair iteration effectively hangs. This is reachable from a degenerate but parseable input schema containing "anyOf": [], via convert_any_of.

Fix

Guard at the top of the function: with fewer than two subschemas there are no pairs to compare, so mutual exclusivity is vacuously true (this preserves the existing behavior for a single subschema, which also produced no pairs). Added a unit test for the empty slice and a conversion-level test that an "anyOf": [] schema converts without panicking.

Trade-offs

An empty anyOf is arguably invalid input, so an alternative would be to reject it with an error during conversion. This change keeps the current behavior (the pipeline already handles it downstream) and only removes the underflow.

@danieleades
danieleades force-pushed the fix/empty-subschemas branch from 1256046 to e3875cd Compare July 21, 2026 07:47
An empty subschema list (e.g. from a degenerate "anyOf": []) caused
`len - 1` to underflow: a panic in debug builds and an effectively
unbounded iteration in release builds. Return true for fewer than two
subschemas since mutual exclusivity is vacuously satisfied.
@danieleades
danieleades force-pushed the fix/empty-subschemas branch from e3875cd to 463eb62 Compare July 28, 2026 08:44

@ahl ahl left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this

subschemas: &'a [Schema],
) -> Result<(TypeEntry, &'a Option<Box<Metadata>>)> {
// An empty `anyOf` cannot match any instance.
if subschemas.is_empty() {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think an empty anyOf is a valid input for what it's worth.

Is this related to the fix?

Comment thread typify-impl/src/util.rs
Comment on lines +51 to +53
// With fewer than two subschemas there are no pairs to compare, so the
// schemas are vacuously mutually exclusive. This also avoids underflow
// in `len - 1` below when `subschemas` is empty.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// With fewer than two subschemas there are no pairs to compare, so the
// schemas are vacuously mutually exclusive. This also avoids underflow
// in `len - 1` below when `subschemas` is empty.
// With fewer than two subschemas, this is a degenerate case where the lone schema is mutually exclusive with everything else (which happens to be nothing).

And please line wrap

Comment thread typify-impl/src/util.rs
Comment on lines +1121 to +1123
// An empty slice of subschemas has no pairs to compare, so it's
// vacuously true. This should not panic (previously `len - 1`
// underflowed for an empty slice).

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// An empty slice of subschemas has no pairs to compare, so it's
// vacuously true. This should not panic (previously `len - 1`
// underflowed for an empty slice).


match &type_space.id_to_entry[&type_id].details {
super::TypeEntryDetails::Enum(details) => assert!(details.variants.is_empty()),
details => panic!("empty anyOf should be uninhabited, got {details:?}"),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think "uninhabited" is a term we use typically. Usually something like "unsatisfiable"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants