Possible unsoundness in the SMT encoding
Closed this issue · 2 comments
Hello,
I've been trying recently, out of curiosity, to change fstar
's backend from z3
to vampire
. I made the discovery that vampire
is able to deduce unsat
from any fstar
file.
The bug in general
Here are the steps to reproduce:
- have an
fstar
file that leads to the generation of ansmt
file when using#push-options "--log_queries"
- make the resulting
smt
file moresmt
compliant, that is
- add the arity to the sort declarations:
(declare-sort Term)
becomes(declare-sort Term 0)
- rewrite the datatype declaration to be valid:
(declare-datatypes () ((Fuel (ZFuel) (SFuel (prec Fuel)))))
becomes(declare-datatypes ((Fuel 0) (((ZFuel) (SFuel (prec Fuel)))))
- turn any
implies
into=>
and anyiff
into=
- remove all the
echo
(this may be a limitation forvampire
)
When this is done, vampire
always returns:
% Warning: check-sat is not the last entry. Skipping the rest!
% Refutation found. Thanks to Tanya!
% SZS status Unsatisfiable for queries-Test3
% SZS output start Proof for queries-Test3
36. ! [X0 : 'Term()',X1 : 'Term()'] : ('HasType'(X0,X1) => 'Valid'(X1)) [input]
114. $false <=> 'Valid'('Prims.l_False') [input]
3182. ! [X0 : 'Term()'] : ('NoHoist'(X0,'HasType'('FStar.Pervasives.with_type@tok','Tm_arrow_99724436653747ac6f5a6a00c64ff8bc')) & ! [X2 : 'Term()',X1 : 'Term()'] : 'FStar.Pervasives.with_type'(X1,X2) = 'ApplyTT'('ApplyTT'('FStar.Pervasives.with_type@tok',X1),X2)) [input]
3184. ! [X0 : 'Term()',X1 : 'Term()'] : ('FStar.Pervasives.with_type'(X0,X1) = X1 & 'HasType'('FStar.Pervasives.with_type'(X0,X1),X0)) [input]
3268. ! [X0 : 'Term()'] : ('NoHoist'(X0,'HasType'('FStar.Pervasives.with_type@tok','Tm_arrow_99724436653747ac6f5a6a00c64ff8bc')) & ! [X1 : 'Term()',X2 : 'Term()'] : 'FStar.Pervasives.with_type'(X2,X1) = 'ApplyTT'('ApplyTT'('FStar.Pervasives.with_type@tok',X2),X1)) [rectify 3182]
3270. ! [X0 : 'Term()'] : ('NoHoist'(X0,bG8) & ! [X1 : 'Term()',X2 : 'Term()'] : 'FStar.Pervasives.with_type'(X2,X1) = 'ApplyTT'('ApplyTT'('FStar.Pervasives.with_type@tok',X2),X1)) [fool elimination 3268]
5908. ~'Valid'('Prims.l_False') [true and false elimination 114]
5909. ~'Valid'('Prims.l_False') [flattening 5908]
7619. ! [X0 : 'Term()',X1 : 'Term()'] : ('Valid'(X1) | ~'HasType'(X0,X1)) [ennf transformation 36]
18245. 'HasType'('FStar.Pervasives.with_type'(X0,X1),X0) [cnf transformation 3184]
18246. 'FStar.Pervasives.with_type'(X0,X1) = X1 [cnf transformation 3184]
18248. 'FStar.Pervasives.with_type'(X2,X1) = 'ApplyTT'('ApplyTT'('FStar.Pervasives.with_type@tok',X2),X1) [cnf transformation 3270]
39240. ~'Valid'('Prims.l_False') [cnf transformation 5909]
39332. 'Valid'(X1) | ~'HasType'(X0,X1) [cnf transformation 7619]
39446. 'ApplyTT'('ApplyTT'('FStar.Pervasives.with_type@tok',X0),X1) = X1 [definition unfolding 18246,18248]
39447. 'HasType'('ApplyTT'('ApplyTT'('FStar.Pervasives.with_type@tok',X0),X1),X0) [definition unfolding 18245,18248]
57751. 'HasType'(X1,X0) [forward demodulation 39447,39446]
68272. 'Valid'(X1) [subsumption resolution 39332,57751]
69295. $false [resolution 68272,39240]
% SZS output end Proof for queries-Test3
% ------------------------------
% Version: Vampire 4.7 (commit 8bd968421 on 2022-11-30 18:30:46 +0000)
% Linked with Z3 4.9.1.0 6ed071b44407cf6623b8d3c0dceb2a8fb7040cee z3-4.8.4-6427-g6ed071b44
% Termination reason: Refutation
% Memory used [KB]: 72664
% Time elapsed: 2.031 s
% ------------------------------
% ------------------------------
A suspicious axiom
Looking deeper, I could find one suspicious axiom:
(assert (! (forall ((@x0 Term) (@x1 Term))
(! (and (= (FStar.Pervasives.with_type @x0
@x1)
@x1)
(HasType (FStar.Pervasives.with_type @x0
@x1)
@x0))
:weight 0
:pattern ((FStar.Pervasives.with_type @x0
@x1))
:qid @with_type_primitive_axiom))
:named @with_type_primitive_axiom))
If I understand correctly, with this axiom, it is possible to prove that anything has any type.
A minimal example
From the following fstar
program
module Test3
#push-options "--log_queries"
irreducible
let f (x:nat) = x
let g (_:unit) =
assert (f 2 == f 3 + f 1)
I can extract the following axioms and declaration:
(declare-datatypes () ((Fuel
(ZFuel)
(SFuel (prec Fuel)))))
(declare-sort Term )
(declare-fun Prims.l_False () Term)
(declare-fun Valid (Term) Bool)
(declare-fun MaxIFuel () Fuel)
(declare-fun HasTypeFuel (Fuel Term Term) Bool)
(define-fun HasType ((x Term) (t Term)) Bool
(HasTypeFuel MaxIFuel x t))
(declare-fun FStar.Pervasives.with_type (Term Term) Term)
(assert (! (iff false
(Valid Prims.l_False))
:named false_interp))
(assert (forall ((e Term) (t Term))
(! (implies (HasType e t)
(Valid t))
:pattern ((HasType e t)
(Valid t))
:qid __prelude_valid_intro)))
(assert (! (forall ((@x0 Term) (@x1 Term))
(! (and (= (FStar.Pervasives.with_type @x0
@x1)
@x1)
(HasType (FStar.Pervasives.with_type @x0
@x1)
@x0))
:weight 0
:pattern ((FStar.Pervasives.with_type @x0
@x1))
:qid @with_type_primitive_axiom))
:named @with_type_primitive_axiom))
(check-sat)
And then even z3
proves unsat
Thanks @puyral!
with_type e t
is quite special and is emitted in F*'s encoding to SMT at specific points where the typechecker has proven internally that e
has type t
. F* disables model-based quantifier instantiation (MBQI) in Z3 and relies, especially for cases like this, on pattern-based quantifier instantiation. Indeed, even in your minimized SMT2 file, if you add (as F* does) (set-option :smt.mbqi false) (set-option :auto-config false)
, Z3 says unknown.
That said, as you point out, when seen as an axiom on its own, it is clearly unsound. Rather than relying on quantifier triggers, we should see if there's a way to remove the with_type construction altogether. Especially if that would enable F* to be used with vampire, that would be really nice. Stay tuned, we'll look into what it takes to remove with_type.
This was more or less unused, I removed it and pushed the changes to the master branch. Closing this issue, please reopen if needed. Thanks!