Skip to content

Fix type system soundness hole#4963

Open
SeanTAllen wants to merge 1 commit intomainfrom
fix-issue-1798-aliased-subtype
Open

Fix type system soundness hole#4963
SeanTAllen wants to merge 1 commit intomainfrom
fix-issue-1798-aliased-subtype

Conversation

@SeanTAllen
Copy link
Member

@SeanTAllen SeanTAllen commented Mar 8, 2026

The subtype checker's arrow type reification had a bug: when comparing two arrow types like this->X! and this->X, viewpoint_reifypair used the left side's typeparamref to reify both sides. Since the typeparamref carries the ephemeral marker, both sides were reified identically, making this->X! and this->X appear equal during pairwise comparison.

This allowed unsound code where an aliased (tag) reference could be returned as its original capability (potentially iso), enabling duplication of unique references.

Three fixes work together:

  1. viewpoint_reifypair now walks both arrow types in parallel so each side uses its own typeparamref with the correct ephemeral marker.

  2. viewpoint_reifytypeparam now searches within the type being reified for the matching typeparamref, preferring the local version's ephemeral marker. This covers call sites in subtype.c and compattype.c that pass one side's typeparamref to reify the other.

  3. alias() now short-circuits for val->A arrow types. A val viewpoint always produces val or tag (both self-aliasing), so aliasing inside the arrow is over-conservative. The stdlib's persistent list had four signatures using val->A! that relied on the old behavior; these are changed to val->A.

This is a breaking change for code that relied on the bug. The most common affected pattern is reading a field into a local variable and returning it from a method with a this->A return type — the local auto-aliases to this->A! which is no longer accepted as a subtype of this->A. Returning the field directly avoids the aliasing.

Closes #1798

@ponylang-main ponylang-main added the discuss during sync Should be discussed during an upcoming sync label Mar 8, 2026
@SeanTAllen SeanTAllen added the changelog - fixed Automatically add "Fixed" CHANGELOG entry on merge label Mar 8, 2026
The subtype checker's arrow type reification had a bug: when comparing
two arrow types like `this->X!` and `this->X`, the function
`viewpoint_reifypair` used the left side's typeparamref to reify both
sides. Since the typeparamref carries the ephemeral marker (! or ^),
this meant both sides were reified with the same marker, making
`this->X!` and `this->X` appear identical during pairwise subtype
comparison.

This allowed code like:
```pony
class Foo
  fun alias[X](x: X!) : X^ =>
    let y : ref->X = consume x
    consume y
```
to compile, which is unsound — it takes an aliased (tag) reference and
returns it as if it were the original capability (potentially iso),
enabling duplication of unique references.

Three fixes work together:

1. `viewpoint_reifypair` now walks both arrow types in parallel so each
   side uses its own typeparamref with the correct ephemeral marker.

2. `viewpoint_reifytypeparam` now searches within the type being reified
   for the matching typeparamref, preferring the local version's
   ephemeral marker over one passed from the other side of a subtype
   check. This covers call sites in subtype.c and compattype.c that
   pass one side's typeparamref to reify the other.

3. `alias()` in alias.c now short-circuits for `val->A` arrow types.
   A val viewpoint always produces val or tag, both of which alias to
   themselves, so aliasing inside the arrow (`val->(A!)`) is
   over-conservative when A=iso: `val->(iso!) = val->tag = tag`, but
   `(val->iso)! = val! = val`. The stdlib's persistent list had four
   signatures using `val->A!` that relied on the old behavior; these
   are changed to `val->A`.

Closes #1798
@SeanTAllen SeanTAllen force-pushed the fix-issue-1798-aliased-subtype branch from 947bc84 to 3902bc8 Compare March 8, 2026 09:32
@SeanTAllen SeanTAllen changed the title Fix soundness hole where X! was accepted as subtype of X Fix type system soundness hole Mar 8, 2026
@SeanTAllen
Copy link
Member Author

@jemc should this also get a "CHANGED" entry?

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

Labels

changelog - fixed Automatically add "Fixed" CHANGELOG entry on merge discuss during sync Should be discussed during an upcoming sync

Projects

None yet

Development

Successfully merging this pull request may close these issues.

this->X! should not be a subtype of this->X

2 participants