seL4 / l4v

seL4 specification and proofs

Home Page:https://sel4.systems

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Have wp warn when resulting in a goal with a schematic assumption.

Xaphiosis opened this issue · comments

In a ccorres or wp proof with a ∃val. x = Some val or anything like that, an accidental clarsimp will introduce new free variable, and if the schematic only depends on x, but now the postcondition relies on val, applying wp will result in a schematic assumption in the goal. The very next simp (because we're trying to avoid clarsimp, right? hence also no wpsimp) will the instantiate that schematic to False and we're in trouble.
In order to at least get a heads-up about this, wp should warn when this happens.

wpfix does not handle these cases, datatype_schem doesn't help either

Discussed a bit with @corlewis so far

In some sense there's two issues here, there's the new free variables that the schematic precondition doesn't depend on, and there's the fact that wp results in a schematic assumption in the goal.

I think the first is going to be hard to deal with. It's outside of the scope of wpfix and the only way to recover if it happens seems to be careful application of simp rules to rewrite the variables back into their original forms. Invoking clarsimp throughout the proof might help: if the assumptions are always in a consistent simp-normal form then the schematics preconditions will hopefully depend on the right variables. This does feel like a fragile strategy though.

The second issue is a bit more concerning, our standard wp setup shouldn't be creating schematic assumptions like that. In the specific case encountered here, I'm pretty sure that it is happening due to hoare_vcg_precond_imp being made a wp_comb rule in https://github.com/seL4/l4v/blob/master/proof/invariant-abstract/Syscall_AI.thy#L1149. I'd say that our current design of wp means that rules like that should only be automatically applied by wp_pre, which has some basic built-in protection to avoid schematic assumptions.

Removing that line will hopefully avoid this issue in this specific case, but who knows what other proofs have accidentally depended on it over the years. Even if we do remove it, adding a warning when wp produces a schematic assumption still seems like a worthwhile idea; it's not generally something that we want the tool to do.

Thanks for tracking down the wp_comb! Do you think you'll have any time/willingness post-deadlines to take a look at de-hoare_vcg_precond_imping? It's not good to have an unsafe rule waiting to explode like that, and I think you're most suited to understanding why it was put in in the first place, and what's the easiest workaround (probably supply ...[wp_comb] in affected lemmas?)

Yeah, for the former there's no good solution. One can block the x \noteq None = (EX y. x = Some y rule, which will stop clarsimp, but that probably won't work on pairs (they have to be reconstituted into fst/snd after a drule sym on the equation). In other proofs I use wpc to give me two paths and assume one to be impossible, but in ccorres proofs it isn't an option that often. I'm a bit hazy on how the EX Some or fst/snd parts get reunited with their original source in the wp proofs, but maybe it'll come together in my head in future proofs.

We should really track down the pairs when we have some time. wpfix should be able to deal with them (or datatype_schem which is called by wpfix), and if it doesn't, maybe something isn't quite working there. The way this is supposed to work is that it replaces ?P (x, y) with ?P' (x, y) x y by proving fst (x, y) = x and snd (x, y) = y. See also the explanation in Datatype_Schematic.

Thanks for tracking down the wp_comb! Do you think you'll have any time/willingness post-deadlines to take a look at de-hoare_vcg_precond_imping? It's not good to have an unsafe rule waiting to explode like that, and I think you're most suited to understanding why it was put in in the first place, and what's the easiest workaround (probably supply ...[wp_comb] in affected lemmas?)

I've started having a look at this and while removing it doesn't affect too many lemmas, it does break more than I expected. So far it's only lemmas that do including no_pre, which stops wp_pre from weakening valid preconditions (and no_fail preconditions). While some of these lemmas have bad proofs which can be rewritten to avoid the no_pre, others are pretty intricate and it would take longer than this is worth to update all of them.

Possible easier workarounds that I can think of are:

  • going through and adding supply hoare_vcg_precond_imp[wp_comb] to any broken proof
  • adding hoare_vcg_precond_imp[wp_comb] to the no_pre bundle, to return to the previous state in all of the lemmas that are most likely to break. This would still leave an unsafe situation in those cases, but my guess is that it wouldn't be as bad, since anyone using no_pre in a new proof will hopefully at least already be thinking about weird behaviour in the preconditions.
  • changing no_pre to classic_wp_pre in any broken proofs. This is a different bundle that (I'm assuming) returns the attributes to how they were before wp_pre was implemented. In particular, it includes hoare_vcg_precond_imp[wp_comb].

Currently, out of those three options my preference would be the third one. The second would be the least work and keep things the closest to the current behaviour, but the third shouldn't take too much more effort and would result in what I think would be the most sane state, with the least amount of unexpected side-effects from including bundles.

  • changing no_pre to classic_wp_pre in any broken proofs. This is a different bundle that (I'm assuming) returns the attributes to how they were before wp_pre was implemented. In particular, it includes hoare_vcg_precond_imp[wp_comb].

Currently, out of those three options my preference would be the third one. The second would be the least work and keep things the closest to the current behaviour, but the third shouldn't take too much more effort and would result in what I think would be the most sane state, with the least amount of unexpected side-effects from including bundles.

I agree, the third option would also be my favourite. I think the classic_wp_pre bundle refers to an earlier, simpler wp_pre implementation, but it doesn't really matter where it comes from if it solves the issue properly.

The line you linked to (https://github.com/seL4/l4v/blob/master/proof/invariant-abstract/Syscall_AI.thy#L1149) also declares hoare_seq_ext as [wp], which is at least strange. Have you tried removing that one as well?

That strange declare hoare_seq_ext[wp] is from a commit by Matthias (still in hg back then, on branch virtual-memory) from 2012. The commit is fairly large and adds a number of these declares mixed in between proofs and other stuff. Most of them have disappeared by now. So definitely worth investigating if this can/should be removed as well.

The line you linked to (https://github.com/seL4/l4v/blob/master/proof/invariant-abstract/Syscall_AI.thy#L1149) also declares hoare_seq_ext as [wp], which is at least strange. Have you tried removing that one as well?

I did try removing that one as well but it broke a proof in a way that was going to require some thought, so I decided to remove hoare_vcg_precond_imp first and then come back to it separately.

I've started having a look at this and while removing it doesn't affect too many lemmas, it does break more than I expected. So far it's only lemmas that do including no_pre, which stops wp_pre from weakening valid preconditions (and no_fail preconditions). While some of these lemmas have bad proofs which can be rewritten to avoid the no_pre, others are pretty intricate and it would take longer than this is worth to update all of them.

Another issue that I've just discovered is that wp (once) doesn't invoke wp_pre and there are several instances where it was instead using hoare_vcg_precond_imp as a wp_comb rule to solve goals.

So far I've been able to remove the (once) and these proofs continue to work, but an alternative would be to add wp_pre to wp (once) in the same way as for normal uses of wp. Thoughts? In some sense it would break the design of wp (once) because it would be applying two rules, which I think is why it wasn't originally done that way.

Another issue that I've just discovered is that wp (once) doesn't invoke wp_pre and there are several instances where it was instead using hoare_vcg_precond_imp as a wp_comb rule to solve goals.

So far I've been able to remove the (once) and these proofs continue to work, but an alternative would be to add wp_pre to wp (once) in the same way as for normal uses of wp. Thoughts? In some sense it would break the design of wp (once) because it would be applying two rules, which I think is why it wasn't originally done that way.

Could we make wp (once) do only the wp_pre step? (when it is applicable)

Then the "once" semantics would be preserved and we still get the wp_pre effect.

The including no_pre thing was added during one of the Isabelle updates to keep existing lemmas working without thoughtful overhaul, from what I remember. Modifying this statement to be whatever you want to keep these working, in particular your preference of classic_wp_pre sounds good to me.

I also agree with wp (once) being able to take an implicit wp_pre step when applicable, if that can be done, even though it might be unexpected in rare situations (I think vcg_step for instance does something like a wp_pre step as an actual step, not 2-in-1, but wp without wp_pre is even stranger).

I like the idea of wp (once) doing wp_pre, either as an explicit step on its own or as an implicit step as part of applying a different rule, but I'm a bit worried that if we try it we're going to run into a lot of edge cases and complications. In particular, either approach would lead to wp_pre being invoked a lot more often than in a normal use of wp , and there's also a cleanup step at the end of wp that might be hard to replicate.

In more detail, wp can be thought of as (wp_pre, wp_fix, resolve_ruleset_tac+, cleanup_tac), and wp (once) is currently a single invocation of resolve_ruleset_tac (where resolve_ruleset_tac is hiding all the complications of wp, wp_comb and wp_split rules).

The optimal result of any changes would be if we could make it so that ((wp (once))+)[1] has the same behaviour as wp, but unfortunately I'm struggling to see that being possible. I guess we could make it so that wp (once) is roughly (wp_pre | wp_fix | resolve_ruleset_tac | cleanup_tac) and depend on wp_pre, wp_fix, and cleanup_tac only being able to be applied a single time to a given goal, but if that even works then it does feel a bit fragile and still results in additional invocations of wp_pre and wp_fix. I don't think this would lead to anything too bad, but I can think of cases that I've seen where the additional wp_pre steps would lead to different behaviour.

I guess we could make it so that wp (once) is roughly (wp_pre | wp_fix | resolve_ruleset_tac | cleanup_tac) and depend on wp_pre, wp_fix, and cleanup_tac only being able to be applied a single time to a given goal

It's a technicality and doesn't really matter, but this would actually need to be wp_pre0, as wp_pre is always successful, even when it doesn't do anything.

So wp_pre0 and wpfix should only be applicable once to a specific goal, i.e. once they have applied they should not be able to apply again. So that would be safe. cleanup_tac I'm not so sure about.

Hang on, wouldn't that make the new wp (once) something like: wp_pre, wp_fix?, resolve_ruleset_tac, cleanup_tac (with the ? being for the methods which can fail) rather than the |?

This conversation is getting me a bit worried about (wp (once))+ drifting away from wp ... but we never had that to begin with, right? For my own clarity: does wp apply wpfix only once, or repeatedly as it goes through rules? I think it makes sense only once for wp, and then since wpsimp alternates clarsimp and wp it does internally repeat wpfix, so that should be fine. /ramble

I was more thinking to make wp (once) only do a single step, no matter which of the sub-tactics that step is actually doing. So with a 'standard' goal, the first use of wp (once) would do wp_pre, the second would do wp_fix (if applicable), and then the next would be the core resolve_ruleset_tac. Depending on what rule that last one applied, the goal might now be in a state where wp_pre can apply again, otherwise further invocations of wp (once) would continue to do resolve_ruleset_tac steps. Eventually, once all of the other tactics no longer apply, then wp (once) would do cleanup_tac. As Gerwin said, it's unclear whether that last cleanup would loop or if the whole process would terminate at that point.