Skip to content

Tags: dfinity/candid

Tags

2026-10-21

Toggle 2026-10-21's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
fix: Bound subtype checks on shared recursive types; release candid 0…

….10.39 (#776)

## What

A subtype check could take exponential time on a recursive type
reachable along many paths. This happened in both `subtype_check_all`
(and so `service_compatibility_report` and `didc check`) and
`subtype`/`subtype_with_config` (and so `service_compatible` and the
decoder's check of `func`/`service` references):

```candid
// new.did: A0 … A22, each level pointing at the next twice
type A0 = record { A1; A1 };
…
type A22 = nat;
service : { go : () -> (A0) }
// old.did: the same shape with B0 … B22, leaf `text`
```

On `master`, `didc check new.did old.did` runs for about 13 s and then
runs out of memory. At depth 18 it reports 262,144 = 2^18 "incompatible
changes", all for the same `nat` → `text`. The `opt` variant `A_i =
record { opt A_{i+1}; opt A_{i+1}; nat }` is exponential in time through
the opt rule's probes.

## Why

A failed pair is withdrawn from `Gamma`, which is necessary because the
opt-rule probes share it and must not assume a failed pair. But nothing
remembered that the pair had failed, so every later path to it checked
it again from scratch. The collector keeps going after errors, and a
probe falls back after failing, so both reach the same pair along every
path.

## Fix

- **Refuted pairs are remembered.** `Assumptions` keeps the pairs the
check has refuted, and `subtype_` fails at once on one of them. This is
sound because assumptions only make a check more permissive: a pair that
fails under some assumptions fails under any.
- **The collector reports each failed pair once.** Its own `failed` set
is separate from the refuted pairs. A later path to a reported pair adds
one entry, `A1 is not a subtype of B1 (see above)`, so a type shared by
two methods is still reported under both. A pair refuted only inside a
probe is reported in full when the collector reaches it.
- **Hitting the recursion limit fails the whole check.** That failure
depends on the stack, not on the types, so it can't be cached. But
re-exploring it would be exponential again, because an opt probe that
hit the limit used to fall back and accept with a warning.
- `RecursionDepth` now counts its overflows. After the first one,
`subtype_` and the collector return at once, the opt fallback no longer
accepts, and nothing the overflow touched is cached.
- As a backstop, `subtype_with_config` and `subtype_check_all` check the
count when they finish. If the limit was hit, they withdraw the check's
assumptions and fail. `subtype_check_all` adds `recursion limit
exceeded; the check stopped there, so this report may be incomplete`.

The depth-64 versions of both examples now finish instantly. The first
reports 65 entries and the `opt` one reports 1.

**Behaviour change:** an opt probe that hits the recursion limit now
fails the check instead of accepting with a warning. Public signatures
are unchanged, and `equal` is unchanged.

## Tests

In `rust/candid_parser/tests/compatibility.rs`:
- `shared_recursive_types_are_checked_once`
- `type_shared_by_two_methods_is_reported_under_both`
- `shared_recursive_types_under_opt_are_probed_once`
- `pair_refuted_by_a_probe_is_still_reported`

`rust/candid/tests/subtype_recursion_limit.rs` runs on a thread with a
fixed 1 MiB stack, after measuring the shortest chain that overflows
there:
- `probe_that_hits_the_limit_fails_the_check`: an opt diamond whose
field only fails by hitting the limit.
- `pair_that_hit_the_limit_is_not_reported_as_incompatible`: a pair that
overflows when reached deep is not reported as "see above" when reached
shallow.
- `overflow_while_tracing_an_alias_fails_the_check`: tries every wrapper
depth around the limit, so the overflow lands while tracing an alias for
one of them.

Each of these fails or hangs without the change it covers. `cargo test`
passes under the three feature sets `rust.yml` uses, and `cargo clippy
--features all --tests -D warnings` is clean.

## Release: candid 0.10.39

Patch release of `candid` / `candid_derive`, 0.10.38 → 0.10.39, cut in
this PR together with the fix. It also ships #775.

- Bump `candid` and `candid_derive` to 0.10.39, and the `=` pin between
them
- Date the changelog entries as `2026-10-21` / `Candid 0.10.39` (#775
and this PR)
- Refresh all four lockfiles: `Cargo.lock`, `rust/bench/Cargo.lock`,
`tools/ui/Cargo.lock`, `rust/candid/fuzz/Cargo.lock` (version bumps
only, no dependency churn)

`candid_parser` stays at 0.4.1; its `candid` dependency is a `0.10.16`
floor.

### Test plan

- [x] `cargo check -p candid -p candid_derive -p candid_parser`
- [x] `cargo test -p candid --features all`: all suites pass
- [x] `cd tools/ui && cargo build --target wasm32-unknown-unknown
--profile canister --package didjs --locked`, the release workflow's
exact command (built against placeholder `dist/` assets, since `npm` was
not available locally)
- [x] `cd rust/candid/fuzz && cargo check`
- [ ] Reviewer to confirm release scope and changelog date

### After merge

1. Tag `2026-10-21` on `master`. `candid-ui-release.yml` triggers on it
to publish the `candid_ui` canister wasm
2. Run the **Crates Publish** workflow (`workflow_dispatch`) with the
`candid` input checked, which publishes `candid_derive` then `candid`

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

2026-09-25

Toggle 2026-09-25's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
chore: Release candid 0.10.37 (#772)

Patch release of `candid` / `candid_derive`, 0.10.36 → 0.10.37. No
source changes — it dates the two decoder fixes already on `master`.

## Summary

- Bump `candid` and `candid_derive` 0.10.36 → 0.10.37, and the `=` pin
between them
- Date the `Unreleased` changelog entries as `2026-09-25` / `Candid
0.10.37`, covering:
+ Bounding the byte length of the type-table header, 64 KiB by default
and configurable with `set_max_header_len` (#770)
+ Bounding the size of a type named in a decoder diagnostic, so its
rendering no longer follows the type's own width and depth (#771)
- Refresh `Cargo.lock`, `rust/bench/Cargo.lock`, `tools/ui/Cargo.lock`
and `rust/candid/fuzz/Cargo.lock`

`candid_parser` is unchanged at 0.4.1; its dependency on `candid` is a
`0.10.16` floor, so it needs no bump.

The lockfile diffs are version bumps only — no dependency churn.

## All four lockfiles this time

The 0.10.36 release noted that `rust/candid/fuzz/Cargo.lock` was stale
at 0.10.34 and that all four lockfiles should move together as a
release-checklist step. This one does that, so `fuzz` goes 0.10.34 →
0.10.37.

`tools/ui` patches `candid` to the workspace path, so its lockfile pins
the path crate's version and has to move with the bump.
`candid-ui-release.yml` builds `didjs` there with `--locked` and
triggers on the date tag, so a stale lock would only fail at tag time,
after merge.

## Test plan

- [x] `cargo check -p candid -p candid_derive -p candid_parser`
- [x] `cargo test -p candid --features all` — all suites pass
- [x] `cd tools/ui && cargo build --target wasm32-unknown-unknown
--profile canister --package didjs --locked` — the release workflow's
exact command, succeeds
- [x] `cd rust/candid/fuzz && cargo check`
- [ ] Reviewer to confirm release scope and changelog date

## After merge

1. Tag `2026-09-25` on `master` — this is what `candid-ui-release.yml`
triggers on to publish the `candid_ui` canister wasm
2. Run the **Crates Publish** workflow (`workflow_dispatch`) with the
`candid` input checked, which publishes `candid_derive` then `candid`

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>

didc-v0.6.2

Toggle didc-v0.6.2's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
ci: fix didc-release on musl with precise-builds (#746)

Fix
https://github.com/dfinity/candid/actions/runs/28178275960/job/83460624957

2025-12-18

Toggle 2025-12-18's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
chore: release candid_parser v0.2.4, didc v0.5.4 (#694)

2025-12-10

Toggle 2025-12-10's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
ci: release candid_ui (#692)

GItHub releases will include prebuilt `candid_ui.wasm` so that tools
like `icp-cli` can integrate.

A test release can be found
[here](https://github.com/dfinity/candid/releases/tag/test_release_candid_ui_2).

2025-10-16

Toggle 2025-10-16's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
chore: release candid v0.10.20, candid_parser v0.2.3, didc v0.5.3 (#687)

2025-10-02

Toggle 2025-10-02's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
chore: release candid_parser v0.2.2, didc v0.5.2 (#682)

Unverified

This tag is not signed, but one or more authors requires that any tag attributed to them is signed.

Unverified

This tag is not signed, but one or more authors requires that any tag attributed to them is signed.

2025-07-25

Toggle 2025-07-25's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
chore: release 2025-07-25 (#653)

- `candid`: 0.10.14 -> 0.10.15
- `candid_derive`: 0.6.6 -> 0.10.15
- `candid_parser`: 0.1.4 -> 0.2.0
- `didc`: 0.4.0 -> 0.5.0