// ******************************************* // * * // * Boogie Procedure Language Prelude * // * * // ******************************************* //------------ New types type real; type elements; type struct; type exposeVersionType; //------------ Encode the heap var $Heap : [ref, name]x where IsHeap($Heap); // IsHeap(h) holds if h is a properly formed heap function IsHeap(h: [ref, name]x) returns (bool); // records whether a pointer refers to allocated data const unique $allocated : name; // the elements from dereferencing an array pointer const unique $elements : name; // the name of the most derived class for which an object invariant holds const unique $inv : name; // the name of (the supertype of) the class for which the object invariant is allowed to be broken const unique $localinv: name; // dummy field that is havoced at unpacks so that it can be used to deduce state changes const unique $exposeVersion : name; // declaration type of exposeVersion is System.Object axiom DeclType($exposeVersion) == System.Object; // the $sharingMode field indicates the object's sharing mode, which is either $SharingMode_Unshared or $SharingMode_LockProtected const unique $sharingMode : name; const unique $SharingMode_Unshared : name; const unique $SharingMode_LockProtected : name; // a reference to the object ($ownerRef, $ownerFrame) const unique $ownerRef : name; const unique $ownerFrame : name; const unique $PeerGroupPlaceholder : name; // used as a type name // a map from class names to their representative "references" used to obtain values of static fields function ClassRepr(class: name) returns (ref); // this map is injective function ClassReprInv(ref) returns (name); axiom (∀ c: name • {ClassRepr(c)} ClassReprInv(ClassRepr(c)) == c); axiom (∀ T:name • ¬($typeof(ClassRepr(T)) <: System.Object)); axiom (∀ T:name • ClassRepr(T) ≠ null); axiom (∀ T:name, h:[ref,name]x • {h[ClassRepr(T),$ownerFrame]} IsHeap(h) ⇒ h[ClassRepr(T),$ownerFrame] == $PeerGroupPlaceholder); //------------ Fields // fields are classified into whether or not the field is static (i.e., it is a field of a ClassRepr "ref") // and whether or not it is directly modifiable by the user // indicates a field is directly modifiable by the user function IsDirectlyModifiableField(f: name) returns (bool); axiom ¬(IsDirectlyModifiableField($allocated)); axiom IsDirectlyModifiableField($elements); // note: $elements is directly modifiable by the user axiom ¬(IsDirectlyModifiableField($inv)); axiom ¬(IsDirectlyModifiableField($localinv)); axiom ¬(IsDirectlyModifiableField($ownerRef)); axiom ¬(IsDirectlyModifiableField($ownerFrame)); axiom ¬(IsDirectlyModifiableField($exposeVersion)); // indicates a field is static function IsStaticField(f: name) returns (bool); axiom ¬IsStaticField($allocated); axiom ¬IsStaticField($elements); axiom ¬IsStaticField($inv); axiom ¬IsStaticField($localinv); axiom ¬IsStaticField($exposeVersion); // indicates if a is included in modifies o.* and o.** function $IncludedInModifiesStar(f: name) returns (bool); axiom ¬$IncludedInModifiesStar($ownerRef); axiom ¬$IncludedInModifiesStar($ownerFrame); // $inv and $localinv are not included either, but we don't need to say that in an axiom axiom $IncludedInModifiesStar($exposeVersion); axiom $IncludedInModifiesStar($elements); //------------ Array elements function ValueArrayGet (elements, int) returns (any); function ValueArraySet (elements, int, any) returns (elements); function IntArrayGet (elements, int) returns (int); function IntArraySet (elements, int, int) returns (elements); function RefArrayGet (elements, int) returns (ref); function RefArraySet (elements, int, ref) returns (elements); axiom (∀ A:elements, i:int, x:any • ValueArrayGet(ValueArraySet(A, i, x), i) == x); axiom (∀ A:elements, i:int, j:int, x:any • i ≠ j ⇒ ValueArrayGet(ValueArraySet(A, i, x), j) == ValueArrayGet(A, j)); axiom (∀ A:elements, i:int, x:int • IntArrayGet(IntArraySet(A, i, x), i) == x); axiom (∀ A:elements, i:int, j:int, x:int • i ≠ j ⇒ IntArrayGet(IntArraySet(A, i, x), j) == IntArrayGet(A, j)); axiom (∀ A:elements, i:int, x:ref • RefArrayGet(RefArraySet(A, i, x), i) == x); axiom (∀ A:elements, i:int, j:int, x:ref • i ≠ j ⇒ RefArrayGet(RefArraySet(A, i, x), j) == RefArrayGet(A, j)); // the indices of multi-dimensional arrays are built up one dimension at a time function ArrayIndex(arr: ref, dim: int, indexAtDim: int, remainingIndexContribution: int) returns (int); // the expressions built up are injective in the indices function ArrayIndexInvX(arrayIndex: int) returns (indexAtDim: int); function ArrayIndexInvY(arrayIndex: int) returns (remainingIndexContribution: int); axiom (∀ a:ref, d:int, x: int, y: int • {ArrayIndex(a,d,x,y)} ArrayIndexInvX(ArrayIndex(a,d,x,y)) == x); axiom (∀ a:ref, d:int, x: int, y: int • {ArrayIndex(a,d,x,y)} ArrayIndexInvY(ArrayIndex(a,d,x,y)) == y); axiom (∀ a:ref, i:int, heap:[ref,name]x • { IntArrayGet(heap[a, $elements], i) } IsHeap(heap) ⇒ InRange(IntArrayGet(heap[a, $elements], i), $ElementType($typeof(a)))); axiom (∀ a:ref, i:int, heap:[ref,name]x • { $typeof(RefArrayGet(heap[a, $elements], i)) } IsHeap(heap) ∧ RefArrayGet(heap[a, $elements], i) ≠ null ⇒ $typeof(RefArrayGet(heap[a, $elements], i)) <: $ElementType($typeof(a))); axiom (∀ a:ref, T:name, i:int, r:int, heap:[ref,name]x • { $typeof(a) <: NonNullRefArray(T, r), RefArrayGet(heap[a, $elements], i) } IsHeap(heap) ∧ $typeof(a) <: NonNullRefArray(T,r) ⇒ RefArrayGet(heap[a, $elements], i) ≠ null); //------------ Array properties: rank, length, dimensions, upper and lower bounds function $Rank (ref) returns (int); axiom (∀ a:ref • 1 ≤ $Rank(a)); axiom (∀ a:ref, T:name, r:int • {$typeof(a) <: RefArray(T,r)} a ≠ null ∧ $typeof(a) <: RefArray(T,r) ⇒ $Rank(a) == r); axiom (∀ a:ref, T:name, r:int • {$typeof(a) <: NonNullRefArray(T,r)} a ≠ null ∧ $typeof(a) <: NonNullRefArray(T,r) ⇒ $Rank(a) == r); axiom (∀ a:ref, T:name, r:int • {$typeof(a) <: ValueArray(T,r)} a ≠ null ∧ $typeof(a) <: ValueArray(T,r) ⇒ $Rank(a) == r); axiom (∀ a:ref, T:name, r:int • {$typeof(a) <: IntArray(T,r)} a ≠ null ∧ $typeof(a) <: IntArray(T,r) ⇒ $Rank(a) == r); function $Length (ref) returns (int); axiom (∀ a:ref • {$Length(a)} 0 ≤ $Length(a) ∧ $Length(a) ≤ int#2147483647); function $DimLength (ref, int) returns (int); // length per dimension up to rank axiom (∀ a:ref, i:int • 0 ≤ $DimLength(a,i)); // The trigger used in the following axiom is restrictive, so that this disjunction is not // produced too easily. Is the trigger perhaps sometimes too restrictive? axiom (∀ a:ref • { $DimLength(a,0) } $Rank(a) == 1 ⇒ $DimLength(a,0) == $Length(a)); function $LBound (ref, int) returns (int); function $UBound (ref, int) returns (int); // Right now we only model C# arrays: axiom (∀ a:ref, i:int • {$LBound(a,i)} $LBound(a,i) == 0); axiom (∀ a:ref, i:int • {$UBound(a,i)} $UBound(a,i) == $DimLength(a,i)-1); // Different categories of arrays are different types const unique $ArrayCategoryValue: name; const unique $ArrayCategoryInt: name; const unique $ArrayCategoryRef: name; const unique $ArrayCategoryNonNullRef: name; function $ArrayCategory(arrayType: name) returns (arrayCategory: name); axiom (∀ T: name, ET: name, r: int • { T <: ValueArray(ET, r) } T <: ValueArray(ET, r) ⇒ $ArrayCategory(T) == $ArrayCategoryValue); axiom (∀ T: name, ET: name, r: int • { T <: IntArray(ET, r) } T <: IntArray(ET, r) ⇒ $ArrayCategory(T) == $ArrayCategoryInt); axiom (∀ T: name, ET: name, r: int • { T <: RefArray(ET, r) } T <: RefArray(ET, r) ⇒ $ArrayCategory(T) == $ArrayCategoryRef); axiom (∀ T: name, ET: name, r: int • { T <: NonNullRefArray(ET, r) } T <: NonNullRefArray(ET, r) ⇒ $ArrayCategory(T) == $ArrayCategoryNonNullRef); //------------ Array types const unique System.Array : name; axiom System.Array <: System.Object; function $ElementType(name) returns (name); function ValueArray (elementType:name, rank:int) returns (name); axiom (∀ T:name, r:int • {ValueArray(T,r)} ValueArray(T,r) <: ValueArray(T,r) ∧ ValueArray(T,r) <: System.Array); function IntArray (elementType:name, rank:int) returns (name); axiom (∀ T:name, r:int • {IntArray(T,r)} IntArray(T,r) <: IntArray(T,r) ∧ IntArray(T,r) <: System.Array); function RefArray (elementType:name, rank:int) returns (name); axiom (∀ T:name, r:int • {RefArray(T,r)} RefArray(T,r) <: RefArray(T,r) ∧ RefArray(T,r) <: System.Array); function NonNullRefArray (elementType:name, rank:int) returns (name); axiom (∀ T:name, r:int • {NonNullRefArray(T,r)} NonNullRefArray(T,r) <: NonNullRefArray(T,r) ∧ NonNullRefArray(T,r) <: System.Array); // arrays of references are co-variant axiom (∀ T:name, U:name, r:int • U <: T ⇒ RefArray(U,r) <: RefArray(T,r)); axiom (∀ T:name, U:name, r:int • U <: T ⇒ NonNullRefArray(U,r) <: NonNullRefArray(T,r)); axiom (∀ A: name, r: int • $ElementType(ValueArray(A,r)) == A); axiom (∀ A: name, r: int • $ElementType(IntArray(A,r)) == A); axiom (∀ A: name, r: int • $ElementType(RefArray(A,r)) == A); axiom (∀ A: name, r: int • $ElementType(NonNullRefArray(A,r)) == A); // subtypes of array types axiom (∀ A: name, r: int, T: name • {T <: RefArray(A,r)} T <: RefArray(A,r) ⇒ T ≠ A ∧ T == RefArray($ElementType(T),r) ∧ $ElementType(T) <: A); axiom (∀ A: name, r: int, T: name • {T <: NonNullRefArray(A,r)} T <: NonNullRefArray(A,r) ⇒ T ≠ A ∧ T == NonNullRefArray($ElementType(T),r) ∧ $ElementType(T) <: A); axiom (∀ A: name, r: int, T: name • {T <: ValueArray(A, r)} T <: ValueArray(A, r) ⇒ T == ValueArray(A, r)); axiom (∀ A: name, r: int, T: name • {T <: IntArray(A, r)} T <: IntArray(A, r) ⇒ T == IntArray(A, r)); // supertypes of array types axiom (∀ A: name, r: int, T: name • {RefArray(A,r) <: T} RefArray(A,r) <: T ⇒ System.Array <: T ∨ (T == RefArray($ElementType(T),r) ∧ A <: $ElementType(T))); axiom (∀ A: name, r: int, T: name • {NonNullRefArray(A,r) <: T} NonNullRefArray(A,r) <: T ⇒ System.Array <: T ∨ (T == NonNullRefArray($ElementType(T),r) ∧ A <: $ElementType(T))); axiom (∀ A: name, r: int, T: name • {ValueArray(A, r) <: T} ValueArray(A, r) <: T ⇒ System.Array <: T ∨ T == ValueArray(A, r)); axiom (∀ A: name, r: int, T: name • {IntArray(A, r) <: T} IntArray(A, r) <: T ⇒ System.Array <: T ∨ T == IntArray(A, r)); function $ArrayPtr (elementType:name) returns (name); //------------ Encode structs function $StructGet (struct, name) returns (any); function $StructSet (struct, name, any) returns (struct); axiom (∀ s:struct, f:name, x:any • $StructGet($StructSet(s, f, x), f) == x); axiom (∀ s:struct, f:name, f':name, x:any • f ≠ f' ⇒ $StructGet($StructSet(s, f, x), f') == $StructGet(s, f')); function ZeroInit(s:struct, typ:name) returns (bool); // TODO: ZeroInit needs axiomatization that says the fields of s are 0 or null or ZeroInit, depending on their types //------------ Encode type information function $typeof (ref) returns (name); function $BaseClass(sub: name) returns (base: name); axiom (∀ T: name • { $BaseClass(T) } T <: $BaseClass(T) ∧ (T != System.Object ==> T != $BaseClass(T))); // Incomparable subtype axiom: function AsDirectSubClass(sub: name, base: name) returns (sub': name); function OneClassDown(sub: name, base: name) returns (directSub: name); axiom (∀ A: name, B: name, C: name • { C <: AsDirectSubClass(B,A) } C <: AsDirectSubClass(B,A) ⇒ OneClassDown(C,A) == B); // primitive types are unordered in the type ordering function $IsValueType(name) returns (bool); axiom (∀ T: name • $IsValueType(T) ⇒ (∀ U: name • T <: U ⇒ T == U) ∧ (∀ U: name • U <: T ⇒ T == U)); // type constructor T[] // const unique System.Object : name; // reflection // function $IsTokenForType (struct, name) returns (bool); function TypeObject (name) returns (ref); // Corresponds with C# typeof(T) const unique System.Type : name; axiom System.Type <: System.Object; axiom (∀ T:name • {TypeObject(T)} $IsNotNull(TypeObject(T), System.Type)); function TypeName(ref) returns (name); // the inverse of TypeObject, which is injective axiom (∀ T:name • {TypeObject(T)} TypeName(TypeObject(T)) == T); function $Is (ref, name) returns (bool); axiom (∀ o:ref, T:name • {$Is(o, T)} $Is(o, T) ⇔ o == null ∨ $typeof(o) <: T); function $IsNotNull(ref, name) returns (bool); axiom (∀ o:ref, T:name • {$IsNotNull(o, T)} $IsNotNull(o, T) ⇔ o ≠ null ∧ $Is(o,T)); // $As(o,T) is to be used only when T denotes a reference type (see also BoxTester). It returns either o or null. function $As (ref, name) returns (ref); axiom (∀ o:ref, T:name • $Is(o, T) ⇒ $As(o, T) == o); axiom (∀ o:ref, T:name • ¬ $Is(o, T) ⇒ $As(o, T) == null); // Arrays are always valid (but may be committed) axiom (∀ h: [ref,name]x, o: ref • {$typeof(o) <: System.Array, h[o,$inv]} IsHeap(h) ∧ o ≠ null ∧ $typeof(o) <: System.Array ⇒ h[o,$inv] == $typeof(o) ∧ h[o,$localinv] == $typeof(o)); //---------- Types and allocation of reachable things function IsAllocated(h: [ref,name]x, o: any) returns (bool); // everything in the range of a proper heap is allocated whenever the domain is axiom (∀ h: [ref,name]x, o: ref, f: name • {IsAllocated(h, h[o,f])} IsHeap(h) ∧ h[o, $allocated] ⇒ IsAllocated(h, h[o,f])); axiom (∀ h: [ref,name]x, o: ref, f: name • {h[h[o,f], $allocated]} IsHeap(h) ∧ h[o, $allocated] ⇒ h[h[o,f], $allocated]); axiom (∀ h: [ref,name]x, s: struct, f: name • {IsAllocated(h, $StructGet(s,f))} IsAllocated(h,s) ⇒ IsAllocated(h, $StructGet(s,f))); axiom (∀ h: [ref,name]x, e: elements, i: int• {IsAllocated(h, RefArrayGet(e,i))} IsAllocated(h,e) ⇒ IsAllocated(h, RefArrayGet(e,i))); axiom (∀ h: [ref,name]x, e: elements, i: int• {IsAllocated(h, ValueArrayGet(e,i))} IsAllocated(h,e) ⇒ IsAllocated(h, ValueArrayGet(e,i))); // no need to have IsAllocated for IntArray's axiom (∀ h: [ref,name]x, o: ref • {h[o, $allocated]} IsAllocated(h,o) ⇒ h[o, $allocated]); axiom (∀ h: [ref,name]x, c:name • {h[ClassRepr(c), $allocated]} IsHeap(h) ⇒ h[ClassRepr(c), $allocated]); const unique $BeingConstructed: ref; const unique $NonNullFieldsAreInitialized: name; axiom DeclType($NonNullFieldsAreInitialized) == System.Object; // types of fields function DeclType(field: name) returns (class: name); // for "class C { T f; ...", DeclType(f) == C function AsNonNullRefField(field: name, T: name) returns (f: name); // for "class C { T! f; ...", AsNonNullRefField(f,T) == f function AsRefField(field: name, T: name) returns (f: name); // for "class C { T f; ...", AsRefField(f,T) == f // for integral types T function AsRangeField(field: name, T: name) returns (f: name); // for "class C { T f; ...", AsRangeField(f,T) == f axiom (∀ f: name, T: name • {AsNonNullRefField(f,T)} AsNonNullRefField(f,T)==f ⇒ AsRefField(f,T)==f); // fields in the heap are well typed axiom (∀ h: [ref,name]x, o: ref, f: name, T: name • {h[o,AsRefField(f,T)]} IsHeap(h) ⇒ $Is(h[o,AsRefField(f,T)], T)); axiom (∀ h: [ref,name]x, o: ref, f: name, T: name • {h[o,AsNonNullRefField(f,T)]} IsHeap(h) ∧ o ≠ null ∧ (o ≠ $BeingConstructed ∨ h[$BeingConstructed, $NonNullFieldsAreInitialized] == true) ⇒ h[o,AsNonNullRefField(f,T)] ≠ null); axiom (∀ h: [ref,name]x, o: ref, f: name, T: name • {h[o,AsRangeField(f,T)]} IsHeap(h) ⇒ InRange(h[o,AsRangeField(f,T)], T)); // abstract classes, interfaces, ... function $IsMemberlessType(name) returns (bool); axiom (∀ o: ref • { $IsMemberlessType($typeof(o)) } ¬$IsMemberlessType($typeof(o))); function $AsInterface(name) returns (name); // this axiom relates a boxed struct to any interfaces that the struct implements // otherwise, all that is known is that a boxed struct is of type System.Object which isn't strong enough axiom (∀ $J: name, s: any, b: ref • { UnboxedType(Box(s,b)) <: $AsInterface($J) } $AsInterface($J) == $J && Box(s,b)==b && UnboxedType(Box(s,b)) <: $AsInterface($J) ==> $typeof(b) <: $J); //------------ Immutable types function $IsImmutable(T:name) returns (bool); // We say here that System.Object is mutable, but only using the $IsImmutable predicate. The functions // $AsImmutable and $AsMutable below are used to say that all subtypes below fixpoints of these functions // are also fixpoints. axiom !$IsImmutable(System.Object); function $AsImmutable(T:name) returns (theType: name); function $AsMutable(T:name) returns (theType: name); axiom (∀ T: name, U:name • {U <: $AsImmutable(T)} U <: $AsImmutable(T) ⇒ $IsImmutable(U) ∧ $AsImmutable(U) == U); axiom (∀ T: name, U:name • {U <: $AsMutable(T)} U <: $AsMutable(T) ⇒ !$IsImmutable(U) ∧ $AsMutable(U) == U); function AsOwner(string: ref, owner: ref) returns (theString: ref); axiom (∀ o: ref , T:name • {$typeof(o) <: $AsImmutable(T)} o ≠ null ∧ o ≠ $BeingConstructed ∧ $typeof(o) <: $AsImmutable(T) ⇒ (∀ h: [ref,name]x • {IsHeap(h)} IsHeap(h) ⇒ h[o, $inv] == $typeof(o) ∧ h[o, $localinv] == $typeof(o) ∧ h[o, $ownerFrame] == $PeerGroupPlaceholder ∧ AsOwner(o, h[o, $ownerRef]) == o ∧ (∀ t: ref • {AsOwner(o, h[t, $ownerRef])} AsOwner(o, h[t, $ownerRef]) == o ⇒ t == o ∨ h[t, $ownerFrame] ≠ $PeerGroupPlaceholder))); //------------ Encode methodology const unique System.String: name; function $StringLength (ref) returns (int); axiom (∀ s:ref • {$StringLength(s)} 0 ≤ $StringLength(s)); // for rep fields function AsRepField(f: name, declaringType: name) returns (theField: name); axiom (∀ h: [ref,name]x, o: ref, f: name, T: name • {h[o,AsRepField(f,T)]} IsHeap(h) ∧ h[o,AsRepField(f,T)] ≠ null ⇒ h[h[o,AsRepField(f,T)], $ownerRef] == o ∧ h[h[o,AsRepField(f,T)], $ownerFrame] == T); // for peer fields function AsPeerField(f: name) returns (theField: name); axiom (∀ h: [ref,name]x, o: ref, f: name • {h[o,AsPeerField(f)]} IsHeap(h) ∧ h[o,AsPeerField(f)] ≠ null ⇒ h[h[o,AsPeerField(f)], $ownerRef] == h[o, $ownerRef] ∧ h[h[o,AsPeerField(f)], $ownerFrame] == h[o, $ownerFrame]); // committed fields are fully valid axiom (∀ h:[ref,name]x, o:ref • {h[h[o,$ownerRef], $inv] <: h[o, $ownerFrame] } IsHeap(h) ∧ h[o,$ownerFrame] ≠ $PeerGroupPlaceholder ∧ h[h[o,$ownerRef], $inv] <: h[o, $ownerFrame] ∧ h[h[o,$ownerRef], $localinv] ≠ $BaseClass(h[o, $ownerFrame]) ⇒ h[o,$inv] == $typeof(o) ∧ h[o,$localinv] == $typeof(o)); // The following procedure sets the owner of o and all its peers to (ow,fr). // It expects o != null && o.$ownerFrame==$PeerGroupPlaceholder, but this condition is checked at the call site. procedure $SetOwner(o: ref, ow: ref, fr: name); modifies $Heap; ensures (∀ p: ref, F: name • { $Heap[p, F] } (F ≠ $ownerRef ∧ F ≠ $ownerFrame) ∨ old($Heap[p, $ownerRef] ≠ $Heap[o, $ownerRef]) ∨ old($Heap[p, $ownerFrame] ≠ $Heap[o, $ownerFrame]) ⇒ old($Heap[p, F]) == $Heap[p, F]); ensures (∀ p: ref • { $Heap[p, $ownerRef] } { $Heap[p, $ownerFrame] } old($Heap[p, $ownerRef] == $Heap[o, $ownerRef]) ∧ old($Heap[p, $ownerFrame] == $Heap[o, $ownerFrame]) ⇒ $Heap[p, $ownerRef] == ow ∧ $Heap[p, $ownerFrame] == fr); // The following procedure is called for "o.f = e;" where f is a rep field declared in a class T: procedure $UpdateOwnersForRep(o: ref, T: name, e: ref); modifies $Heap; ensures (∀ p: ref, F: name • { $Heap[p, F] } (F ≠ $ownerRef ∧ F ≠ $ownerFrame) ∨ old($Heap[p, $ownerRef] ≠ $Heap[e, $ownerRef]) ∨ old($Heap[p, $ownerFrame] ≠ $Heap[e, $ownerFrame]) ⇒ old($Heap[p, F]) == $Heap[p, F]); ensures e == null ⇒ $Heap == old($Heap); ensures e ≠ null ⇒ (∀ p: ref • { $Heap[p, $ownerRef] } { $Heap[p, $ownerFrame] } old($Heap[p, $ownerRef] == $Heap[e, $ownerRef]) ∧ old($Heap[p, $ownerFrame] == $Heap[e, $ownerFrame]) ⇒ $Heap[p, $ownerRef] == o ∧ $Heap[p, $ownerFrame] == T); // The following procedure is called for "c.f = d;" where f is a peer field: procedure $UpdateOwnersForPeer(c: ref, d: ref); modifies $Heap; ensures (∀ p: ref, F: name • { $Heap[p, F] } (F ≠ $ownerRef ∧ F ≠ $ownerFrame) ∨ old($Heap[p, $ownerRef] ≠ $Heap[d, $ownerRef] ∨ $Heap[p, $ownerFrame] ≠ $Heap[d, $ownerFrame]) ⇒ old($Heap[p, F]) == $Heap[p, F]); ensures d == null ⇒ $Heap == old($Heap); ensures d ≠ null ⇒ (∀ p: ref • { $Heap[p, $ownerRef] } { $Heap[p, $ownerFrame] } old($Heap[p, $ownerRef] == $Heap[d, $ownerRef] ∧ $Heap[p, $ownerFrame] == $Heap[d, $ownerFrame]) ⇒ $Heap[p, $ownerRef] == old($Heap)[c, $ownerRef] ∧ $Heap[p, $ownerFrame] == old($Heap)[c, $ownerFrame]); // Intuitively, the $FirstConsistentOwner field of an object is defined as the closest // transitive owner that is consistent. The field is defined if the object is committed. const unique $FirstConsistentOwner: name; function $AsPureObject(ref) returns (ref); // used only for triggering function ##FieldDependsOnFCO(o: ref, f: name, ev: exposeVersionType) returns (value: any); // The following axiom say that for any committed object o, each field of o is determined // not by the exposeVersion of o's first consistent owner. axiom (∀ o: ref, f: name, h: [ref,name]x • { h[$AsPureObject(o), f] } IsHeap(h) ∧ o ≠ null ∧ h[o, $allocated] == true ∧ h[o, $ownerFrame] ≠ $PeerGroupPlaceholder ∧ h[h[o, $ownerRef], $inv] <: h[o, $ownerFrame] ∧ h[h[o, $ownerRef], $localinv] ≠ $BaseClass(h[o, $ownerFrame]) ⇒ h[o, f] == ##FieldDependsOnFCO(o, f, h[h[o, $FirstConsistentOwner], $exposeVersion])); axiom (∀ o: ref, h: [ref,name]x • { h[o, $FirstConsistentOwner] } IsHeap(h) ∧ o ≠ null ∧ h[o, $allocated] == true ∧ h[o, $ownerFrame] ≠ $PeerGroupPlaceholder ∧ h[h[o, $ownerRef], $inv] <: h[o, $ownerFrame] ∧ h[h[o, $ownerRef], $localinv] ≠ $BaseClass(h[o, $ownerFrame]) ⇒ h[o, $FirstConsistentOwner] != null ∧ h[h[o, $FirstConsistentOwner], $allocated] == true ∧ // ¬ h[h[o, $FirstConsistentOwner], Committed] (h[h[o, $FirstConsistentOwner], $ownerFrame] == $PeerGroupPlaceholder ∨ ¬(h[h[h[o, $FirstConsistentOwner], $ownerRef], $inv] <: h[h[o, $FirstConsistentOwner], $ownerFrame]) ∨ h[h[h[o, $FirstConsistentOwner], $ownerRef], $localinv] == $BaseClass(h[h[o, $FirstConsistentOwner], $ownerFrame]))); //---------- Boxed and unboxed values // Unboxing is functional, but boxing is not function Box (any, ref) returns (ref); // first arg is any because of reals and other non-int values function Unbox (ref) returns (any); // For simplicity, we track boxed values stored to locals, not those stored into the heap. axiom (∀ x:any, p:ref • {Unbox(Box(x,p))} Unbox(Box(x,p)) == x); function UnboxedType(ref) returns (name); // Boxes are always consistent axiom (∀ p: ref • {$IsValueType(UnboxedType(p))} $IsValueType(UnboxedType(p)) ⇒ (∀ heap: [ref,name]x, x: any • {heap[Box(x,p),$inv]} IsHeap(heap) ⇒ heap[Box(x,p),$inv] == $typeof(Box(x,p)) ∧ heap[Box(x,p),$localinv] == $typeof(Box(x,p)))); // For reference types, boxing returns the reference axiom (∀ x:any, p:ref • {UnboxedType(Box(x,p)) <: System.Object} UnboxedType(Box(x,p)) <: System.Object ∧ Box(x,p) == p ⇒ x == p); // BoxTester is the value type equivalent of $As function BoxTester(p:ref, typ: name) returns (ref); axiom (∀ p:ref, typ: name • {BoxTester(p,typ)} UnboxedType(p) == typ ⇔ BoxTester(p,typ) ≠ null); //---------- Various sized integers const unique System.SByte : name; // sbyte axiom $IsValueType(System.SByte); const unique System.Byte : name; // byte axiom $IsValueType(System.Byte); const unique System.Int16 : name; //short axiom $IsValueType(System.Int16); const unique System.UInt16 : name; // ushort axiom $IsValueType(System.UInt16); const unique System.Int32 : name; // int axiom $IsValueType(System.Int32); const unique System.UInt32 : name; // uint axiom $IsValueType(System.UInt32); const unique System.Int64 : name; // long axiom $IsValueType(System.Int64); const unique System.UInt64 : name; // ulong axiom $IsValueType(System.UInt64); const unique System.Char : name; // char axiom $IsValueType(System.Char); const unique System.UIntPtr : name; axiom $IsValueType(System.UIntPtr); const unique System.IntPtr : name; axiom $IsValueType(System.IntPtr); const unique int#m2147483648: int; // System.Int32.MinValue const unique int#2147483647: int; // System.Int32.MaxValue const unique int#4294967295: int; // System.UInt32.MaxValue const unique int#m9223372036854775808: int; // System.Int64.MinValue const unique int#9223372036854775807: int; // System.Int64.MaxValue const unique int#18446744073709551615: int; // System.UInt64.MaxValue axiom int#m9223372036854775808 < int#m2147483648; axiom int#m2147483648 < -100000; axiom 100000 < int#2147483647; axiom int#2147483647 < int#4294967295; axiom int#4294967295 < int#9223372036854775807; axiom int#9223372036854775807 < int#18446744073709551615; axiom int#m9223372036854775808 + 1 == - int#9223372036854775807; axiom int#m2147483648 + 1 == - int#2147483647; function InRange(i: int, T: name) returns (bool); axiom (∀ i:int • InRange(i, System.SByte) ⇔ -128 ≤ i ∧ i < 128); axiom (∀ i:int • InRange(i, System.Byte) ⇔ 0 ≤ i ∧ i < 256); axiom (∀ i:int • InRange(i, System.Int16) ⇔ -32768 ≤ i ∧ i < 32768); axiom (∀ i:int • InRange(i, System.UInt16) ⇔ 0 ≤ i ∧ i < 65536); axiom (∀ i:int • InRange(i, System.Int32) ⇔ int#m2147483648 ≤ i ∧ i ≤ int#2147483647); axiom (∀ i:int • InRange(i, System.UInt32) ⇔ 0 ≤ i ∧ i ≤ int#4294967295); axiom (∀ i:int • InRange(i, System.Int64) ⇔ int#m9223372036854775808 ≤ i ∧ i ≤ int#9223372036854775807); axiom (∀ i:int • InRange(i, System.UInt64) ⇔ 0 ≤ i ∧ i ≤ int#18446744073709551615); axiom (∀ i:int • InRange(i, System.Char) ⇔ 0 ≤ i ∧ i < 65536); //---------- Type conversions and sizes function $IntToInt(val: int, fromType: name, toType: name) returns (int); function $IntToReal(int, fromType: name, toType: name) returns (real); function $RealToInt(real, fromType: name, toType: name) returns (int); function $RealToReal(val: real, fromType: name, toType: name) returns (real); axiom (∀ z: int, B: name, C: name • InRange(z, C) ⇒ $IntToInt(z, B, C) == z); function $SizeIs (name, int) returns (bool); // SizeIs(T,n) means that n = sizeof(T) //------------ Formula/term operators function $IfThenElse (bool, any, any) returns (any); axiom (∀ b:bool, x:any, y:any • {$IfThenElse(b,x,y)} b ⇒ $IfThenElse(b,x,y) == x); axiom (∀ b:bool, x:any, y:any • {$IfThenElse(b,x,y)} ¬b ⇒ $IfThenElse(b,x,y) == y); //------------ Bit-level operators function #neg (int) returns (int); function #and (int, int) returns (int); function #or (int, int) returns (int); function #xor (int, int) returns (int); function #shl (int, int) returns (int); function #shr (int, int) returns (int); function #rneg(real) returns (real); function #radd(real, real) returns (real); function #rsub(real, real) returns (real); function #rmul(real, real) returns (real); function #rdiv(real, real) returns (real); function #rmod(real, real) returns (real); function #rLess(real, real) returns (bool); function #rAtmost(real, real) returns (bool); function #rEq(real, real) returns (bool); function #rNeq(real, real) returns (bool); function #rAtleast(real, real) returns (bool); function #rGreater(real, real) returns (bool); //----------- Properties of operators // the connection between % and / axiom (∀ x:int, y:int • {x % y} {x / y} x % y == x - x / y * y); // remainder is C# is complicated, because division rounds toward 0 axiom (∀ x:int, y:int • {x % y} 0 ≤ x ∧ 0 < y ⇒ 0 ≤ x % y ∧ x % y < y); axiom (∀ x:int, y:int • {x % y} 0 ≤ x ∧ y < 0 ⇒ 0 ≤ x % y ∧ x % y < -y); axiom (∀ x:int, y:int • {x % y} x ≤ 0 ∧ 0 < y ⇒ -y < x % y ∧ x % y ≤ 0); axiom (∀ x:int, y:int • {x % y} x ≤ 0 ∧ y < 0 ⇒ y < x % y ∧ x % y ≤ 0); axiom (∀ x:int, y:int • {(x + y) % y} 0 ≤ x ∧ 0 ≤ y ⇒ (x + y) % y == x % y); // do we need this symmetric one, too? axiom (∀ x:int, y:int • {(y + x) % y} 0 ≤ x ∧ 0 ≤ y ⇒ (y + x) % y == x % y); axiom (∀ x:int, y:int • {(x - y) % y} 0 ≤ x-y ∧ 0 ≤ y ⇒ (x - y) % y == x % y); // the following axiom prevents a matching loop in Simplify // axiom (∀ x:int, y:int • {x * y / y * y} x * y / y * y == x * y); // the following axiom has some unfortunate matching, but it does state a property about % that // is sometime useful axiom (∀ a: int, b: int, d: int • { a % d, b % d } 2 ≤ d ∧ a % d == b % d ∧ a < b ⇒ a + d ≤ b); /***** These axioms provide good functionality, but in some cases they can be very expensive // distributions of * and +/- axiom (∀ x: int, y: int, z: int • { (x+y)*z } (x+y)*z == x*z + y*z); axiom (∀ x: int, y: int, z: int • { (x-y)*z } (x-y)*z == x*z - y*z); axiom (∀ x: int, y: int, z: int • { z*(x+y) } z*(x+y) == z*x + z*y); axiom (∀ x: int, y: int, z: int • { z*(x-y) } z*(x-y) == z*x - z*y); *****/ axiom (∀ x: int, y: int • { #and(x,y) } 0 ≤ x ∨ 0 ≤ y ⇒ 0 ≤ #and(x,y)); axiom (∀ x: int, y: int • { #or(x,y) } 0 ≤ x ∧ 0 ≤ y ⇒ 0 ≤ #or(x,y) ∧ #or(x,y) ≤ x + y); axiom (∀ i:int • {#shl(i,0)} #shl(i,0) == i); axiom (∀ i:int, j:int • {#shl(i,j)} 1 ≤ j ⇒ #shl(i,j) == #shl(i,j-1) * 2); axiom (∀ i:int, j:int • {#shl(i,j)} 0 ≤ i ∧ i < 32768 ∧ 0 ≤ j ∧ j ≤ 16 ⇒ 0 ≤ #shl(i, j) ∧ #shl(i, j) ≤ int#2147483647); axiom (∀ i:int • {#shr(i,0)} #shr(i,0) == i); axiom (∀ i:int, j:int • {#shr(i,j)} 1 ≤ j ⇒ #shr(i,j) == #shr(i,j-1) / 2); function #min(int, int) returns (int); function #max(int, int) returns (int); axiom (∀ x: int, y: int • { #min(x,y) } (#min(x,y) == x ∨ #min(x,y) == y) ∧ #min(x,y) ≤ x ∧ #min(x,y) ≤ y); axiom (∀ x: int, y: int • { #max(x,y) } (#max(x,y) == x ∨ #max(x,y) == y) ∧ x ≤ #max(x,y) ∧ y ≤ #max(x,y)); //---------- Properties of String (Literals) function #System.String.IsInterned$System.String$notnull([ref,name]x, ref) returns (ref); function #System.String.Equals$System.String([ref,name]x, ref, ref) returns (bool); function #System.String.Equals$System.String$System.String([ref,name]x, ref, ref) returns (bool); function ##StringEquals(ref, ref) returns (bool); // two names for String.Equals axiom (∀ h: [ref,name]x, a: ref, b: ref • { #System.String.Equals$System.String(h, a, b) } #System.String.Equals$System.String(h, a, b) == #System.String.Equals$System.String$System.String(h, a, b)); // String.Equals is independent of the heap, and it is reflexive and commutative axiom (∀ h: [ref,name]x, a: ref, b: ref • { #System.String.Equals$System.String$System.String(h, a, b) } #System.String.Equals$System.String$System.String(h, a, b) == ##StringEquals(a, b) ∧ #System.String.Equals$System.String$System.String(h, a, b) == ##StringEquals(b, a) ∧ (a == b ⇒ ##StringEquals(a, b))); // String.Equals is also transitive axiom (∀ a: ref, b: ref, c: ref • ##StringEquals(a, b) ∧ ##StringEquals(b, c) ⇒ ##StringEquals(a, c)); // equal strings have the same interned ref axiom (∀ h: [ref,name]x, a: ref, b: ref • { #System.String.Equals$System.String$System.String(h, a, b) } a ≠ null ∧ b ≠ null ∧ #System.String.Equals$System.String$System.String(h, a, b) ⇒ #System.String.IsInterned$System.String$notnull(h, a) == #System.String.IsInterned$System.String$notnull(h, b)); //-----------HACK for unknown value const unique $UnknownRef : ref; // ************** END PRELUDE **************