Skip to content

Fix swap when exception are in call - #1137

Closed
lyonel2017 wants to merge 1 commit into
mainfrom
fix-exception-swap-call
Closed

lyonel2017 wants to merge 1 commit into
mainfrom
fix-exception-swap-call

Conversation

@lyonel2017

@lyonel2017 lyonel2017 commented Sep 11, 2026 •

Copy link
Copy Markdown
Contributor

Fix for #1129

@lyonel2017
lyonel2017 force-pushed the fix-exception-swap-call branch 2 times, most recently from 06b7e6d to 0363e9a Compare September 11, 2026 14:25
@lyonel2017
lyonel2017 marked this pull request as ready for review September 11, 2026 15:26
@strub

strub commented Oct 8, 2026

Copy link
Copy Markdown
Member

Thanks for this fix. We went for a new PR, #1188, which supersedes this one, for the following reasons.

  • Too restrictive. Refusing every swap on a hoare goal with exceptional postconditions also rejects swaps that cannot change what the exceptional postcondition observes, e.g. exchanging two assignments next to a raise (swap 1 1 and swap 4 1 in tests/exception/exception_swap.ec, which had to become fail). Only moving code across something that may raise is a problem.
  • Too permissive elsewhere. The syntactic raise check of Tactic swap is unsound in the presence of raise #926 was removed, so on goals without exceptional postconditions (and in the other logics) swap now moves code across a raise.
  • Calls were still the real gap. swap's raise check (#926) does not look inside called procedures #1129 is about a raise inside a called procedure. fix: unlisted exceptions are unconstrained; no swap across raising calls #1188 makes swap refuse, on hoare goals that constrain exceptions, blocks that may raise: a raise, a call to a procedure whose body may raise (transitively), a call to an abstract procedure (assumed to possibly raise), or an abstract instruction. fission / fusion had the same gap (a FIXME) and get the same check.
  • What "no exceptional postcondition" means. Deciding when raising is observable required fixing the meaning of an exception that is neither named nor covered by a default branch _ => Q. The abstract procedure rule already treats it as unconstrained (hoare [A.f : I ==> I] holds for every instance of A, including raising ones), so fix: unlisted exceptions are unconstrained; no swap across raising calls #1188 makes this explicit everywhere (_ => true): wp gives true instead of failing, and conseq / call no longer fail with Failure "no default exception". A goal therefore constrains exceptions only when it has a branch other than true; otherwise raising is as good as not terminating, which swapping independent blocks preserves.

The tests of #1129 are included in #1188 (tests/exception/exception_default.ec). Closing in favour of #1188.

@strub strub closed this Oct 8, 2026
strub added a commit that referenced this pull request Oct 8, 2026
A hoare postcondition names exceptions (`| e x => Q`) and may have a
default branch (`| _ => Q`). An exception covered by neither had no
consistent meaning:
- `wp` refused a `raise` of it ("missing postcondition for
  exception");
- `conseq` / `call` failed with an anomaly (`Failure "no default
  exception"`) when the premise named an exception the goal did not,
  and ignored the default branch of the goal when the premise had none;
- the abstract procedure rule (`proc I`) proves
  `hoare [A.f : I ==> I]` for every instance of `A`, including ones that
  raise: it treats such exceptions as unconstrained.

Such an exception is now unconstrained, as if `_ => true`: `wp` gives
`true`, and `conseq` / `call` read a missing default as `true` (a
default branch of the goal must then hold on its own). `wp` also no
longer applies the default branch, which binds nothing, to the
arguments of the exception.

`swap` refused blocks containing a `raise`, but not calls to
procedures that raise (#1129): moving `y <- 1` in front of a call that
raises changes the state the exceptional postcondition sees. Raising is
only observable when the goal constrains exceptions (a hoare goal with
a branch other than `true`); otherwise it is as good as not
terminating, which swapping independent blocks preserves. `swap` now
also refuses, on such goals, blocks that may raise: a call to a
procedure whose body may raise, to an abstract procedure (assumed to
possibly raise), or an abstract instruction. `fission` / `fusion` had
the same gap (a FIXME) and get the same check. Blocks that cannot
raise, e.g. two assignments next to a `raise`, are still swapped.

This supersedes #1137, which refused every swap on a hoare goal with
exceptional postconditions, and allowed swapping a `raise` otherwise.

Fixes #1129.

Origin: exceptions were introduced in bba1f1b (r2026.03); the `raise`
check of `swap` in 6dbd99d (#926).

Test: tests/exception/exception_default.ec (#1129, abstract and
nested calls, fission, `wp` and `conseq` with missing defaults; the
previous build fails on it). unit (120 files), stdlib (128) and
examples (49) pass.
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