// // Copyright (c) Microsoft Corporation. All rights reserved. // /***************************************************************************** ****************************************************************************** **** AXIOMS AND TRUSTED DEFINITIONS ****************************************************************************** *****************************************************************************/ ////////////////////////////////////////////////////////////////////////////// // TRIGGERS ////////////////////////////////////////////////////////////////////////////// // Triggers for quantifiers // We could use a single trigger for all values; the distinction between the // various triggers below is just to help the prover run fast. // TV is a trigger for values in general, including addresses. function{:expand false} TV(val:int) returns (bool) { true } // TO is a trigger specifically for word offsets from addresses, where // word offset n is byte offset 4 * n. function{:expand false} TO(wordOffset:int) returns (bool) { true } // TSlot is a trigger for slots in sparse tags function{:expand false} TSlot(slot:int) returns (bool) { true } // TT is a trigger for tables function{:expand false} TT(table:int) returns (bool) { true } ////////////////////////////////////////////////////////////////////////////// // WORDS ////////////////////////////////////////////////////////////////////////////// // i1 <= x < i2 function between(i1:int, i2:int, x:int) returns(bool) { i1 <= x && x < i2 } // valid 32-bit unsigned words // word(i) <==> 0 <= i < 2^32 const WORD_HI:int; // 2^32 axiom WORD_HI >= 100; // REVIEW: should we go ahead and set WORD_HI to exactly 2^32? function word(val:int) returns(bool) { 0 <= val && val < WORD_HI } // converts 2's complement 32-bit val into signed integer function asSigned(val:int) returns(int); // null value(s) const NULL: int; axiom NULL == 0; ////////////////////////////////////////////////////////////////////////////// // ASSEMBLY LANGUAGE ////////////////////////////////////////////////////////////////////////////// // invariant: word(r) for each register r // To maintain invariant, simply check word(exp) for each assignment r := exp // invariant: word(r) for each word w in memory // To maintain invariant, simply check word(exp) for each store of exp to memory var eax:int; var ebx:int; var ecx:int; var edx:int; var esi:int; var edi:int; var ebp:int; var esp:int; 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{:expand false} TVM(a:int, b:int) returns(bool) { true } function Mult(a:int, b:int) returns(int); axiom (forall a:int, b:int::{TVM(a, b)} Mult(a, b) == a * b); // REVIEW: one would hope that this axiom is derivable from // Mult(a, b) == a * b, using b = b1 + b2, but Z3 couldn't seem to do it yet: function{:expand false} TVM3(a:int, b1:int, b2:int) returns(bool) { true } axiom (forall a:int, b1:int, b2:int::{TVM3(a, b1, b2)} Mult(a, b1 + b2) == a * (b1 + b2)); procedure Add(x:int, y:int) returns(ret:int); requires word(x + y); ensures ret == x + y; procedure Sub(x:int, y:int) returns(ret:int); requires word(x - y); ensures ret == x - y; procedure Mul(x:int, y:int) returns(ret:int, hi:int); requires word(x * y); ensures ret == x * y; ensures ret == Mult(x, y); // Note: we only support 32-bit division, so the upper 32 bits must be 0 procedure Div(x:int, zero:int, y:int) returns(ret:int, rem:int); requires zero == 0; requires y != 0; ensures word(ret); ensures word(rem); ensures ret == x / y && ret * y + rem == x; ensures rem == x % y && rem < y; procedure Not(x:int) returns(ret:int); ensures ret == neg(x); ensures word(ret); procedure And(x1:int, x2:int) returns(ret:int); ensures ret == and(x1, x2); ensures word(ret); procedure Or(x1:int, x2:int) returns(ret:int); ensures ret == or(x1, x2); ensures word(ret); procedure Xor(x1:int, x2:int) returns(ret:int); ensures ret == xor(x1, x2); ensures word(ret); procedure Shl(x1:int, x2:int) returns(ret:int); requires x2 < 32; ensures ret == shl(x1, x2); ensures word(ret); procedure Shr(x1:int, x2:int) returns(ret:int); requires x2 < 32; ensures ret == shr(x1, x2); ensures word(ret); // run-time overflow checked procedure AddChecked(x:int, y:int) returns(ret:int); ensures word(x + y); ensures ret == x + y; // run-time overflow checked procedure SubChecked(x:int, y:int) returns(ret:int); ensures word(x - y); ensures ret == x - y; // run-time overflow checked procedure MulChecked(x:int, y:int) returns(ret:int, hi:int); ensures word(x * y); ensures word(Mult(x, y)); ensures ret == x * y; ensures ret == Mult(x, y); procedure Lea(addr:int) returns(ret:int); requires word(addr); ensures ret == addr; procedure LeaUnchecked(addr:int) returns(ret:int); ensures word(ret); // REVIEW: add more general support for signed arithmetic? procedure LeaSignedIndex(base:int, scale:int, index:int, offset:int) returns(ret:int); requires scale == 1 || scale == 2 || scale == 4 || scale == 8; requires word(base + scale * asSigned(index) + offset); ensures ret == base + scale * asSigned(index) + offset; procedure DebugBreak(); ensures false; // DebugBreak never returns. ////////////////////////////////////////////////////////////////////////////// // READ-ONLY MEMORY ////////////////////////////////////////////////////////////////////////////// function roS8(ptr:int) returns(int); function roU8(ptr:int) returns(int); function roS16(ptr:int) returns(int); function roU16(ptr:int) returns(int); function ro32(ptr:int) returns(int); function inRo(ptr:int, size:int) returns(bool); // read and zero-extend 8 bits procedure RoLoadU8(ptr:int) returns (val:int); requires inRo(ptr, 1); ensures word(val); ensures val == roU8(ptr); // read and sign-extend 8 bits procedure RoLoadS8(ptr:int) returns (val:int); requires inRo(ptr, 1); ensures word(val); ensures asSigned(val) == roS8(ptr); // read and zero-extend 16 bits procedure RoLoadU16(ptr:int) returns (val:int); requires inRo(ptr, 2); ensures word(val); ensures val == roU16(ptr); // read and sign-extend 16 bits procedure RoLoadS16(ptr:int) returns (val:int); requires inRo(ptr, 2); ensures word(val); ensures asSigned(val) == roS16(ptr); procedure RoLoad32(ptr:int) returns (val:int); requires inRo(ptr, 4); ensures word(val); ensures val == ro32(ptr); ////////////////////////////////////////////////////////////////////////////// // GC-CONTROLLED MEMORY ////////////////////////////////////////////////////////////////////////////// // valid gc-controlled addresses (must be disjoint from null values) // warning: because of interior pointers, ?gcHi must be a 32-bit word // (it can equal 2^32 - 1, but not 2^32) const ?gcLo: int; const ?gcHi: int; axiom NULL < ?gcLo; axiom ?gcLo <= ?gcHi; axiom ?gcHi < WORD_HI; function gcAddr(i:int) returns (bool) {?gcLo <= i && i < ?gcHi} function gcAddrEx(i:int) returns (bool) {?gcLo <= i && i <= ?gcHi} // Aligned(i) <==> i is a multiple of 4 function Aligned(i:int) returns(bool); axiom (forall i:int, j:int::{TV(i), TO(j)} TV(i) && TO(j) ==> Aligned(i) == Aligned(i + 4 * j)); axiom Aligned(?gcLo); axiom Aligned(?gcHi); // $GcMem[i] = data at address i, if gcAddr(i) and Aligned(i). var $GcMem:[int]int; // Do not modify directly! Use procedures below. // Read a word from gc-controlled memory procedure GcLoad(ptr:int) returns (val:int); requires TV(ptr); requires gcAddr(ptr); requires Aligned(ptr); ensures word(val); ensures TV(val); ensures val == $GcMem[ptr]; // Write a word to gc-controlled memory procedure GcStore(ptr:int, val:int); requires gcAddr(ptr); requires Aligned(ptr); requires word(val); requires TV(ptr) && TV(val); modifies $GcMem; ensures $GcMem == old($GcMem)[ptr := val]; // MAP_ZERO maps all addresses to value zero. const MAP_ZERO:[int]int; axiom (forall i:int::{TV(i)} TV(i) ==> MAP_ZERO[i] == 0); ////////////////////////////////////////////////////////////////////////////// // ABSTRACT NODES ////////////////////////////////////////////////////////////////////////////// // The mutator controls an abstract graph consisting of abstract nodes, // which contain abstract values. These abstract values may be // abstract primitive values or abstract edges pointing to abstract nodes. // Abstract values are represented by integers. For any integer A, // we may interpret A as an abstract primitive value or an abstract pointer: // - the abstract primitive value A represents the concrete integer A // - the abstract pointer A may be one of the following: // - NO_ABS, representing no value (used for partial maps) // - any other value, representing a node in the abstract graph // Any primitive value A will satisfy word(A). To avoid confusion // between abstract primitive and abstract pointer values, the // mutator should choose abstract pointer values A such that !word(A). const NO_ABS:int; // the value "none" // Each abstract object node A has some number of fields, // which is chosen when A is allocated, and never changes afterwards. function numFields(abs:int) returns (int); // $AbsMem represents of the abstract graph's edges: for abstract node A // and field j, $AbsMem[A][j] is the abstract node pointed to by A's field j. // Do not modify $AbsMem directly! $AbsMem is controlled by the mutator. var $AbsMem:[int][int]int; ////////////////////////////////////////////////////////////////////////////// // REACHABILITY ////////////////////////////////////////////////////////////////////////////// type Time; var $Time:Time; // reached(A,T) means that the GC has reached abstract node A at some time // after the initial time T. Initially (at time T), the mutator will // say that reached(root, T). After that, the GC calls the "reach" // ghost procedure to generate reached(A, T) for other A. function reached(a:int, t:Time) returns (bool); // If we've reached A, and A points to A', then reach A'. // Note: this depends on $AbsMem being defined by the mutator. The GC // should not write to $AbsMem directly (if it did, then it could reach // anything, trivially). procedure reach($a:int, $j:int, $t:Time); requires reached($a, $t); requires $AbsMem[$a][$j] != NO_ABS; ensures reached($AbsMem[$a][$j], $t); ////////////////////////////////////////////////////////////////////////////// // POINTERS AND VALUES ////////////////////////////////////////////////////////////////////////////// function Pointer(rev:[int]int, ptr:int, abs:int) returns (bool) { gcAddr(ptr) && Aligned(ptr) && abs != NO_ABS && rev[ptr] == abs // AbsMapped(ptr,rev,abs) } // An interior pointer ("interiorPtr") points to an actual // object address ("ptr") plus some offset ("offset"): // !nullAddr(interiorPtr) ==> interiorPtr == ptr + offset && 0 <= offset && offset <= 4 * numFields($toAbs[ptr - 4]) - 4; function InteriorValue(isPtr:bool, rev:[int]int, val:int, abs:int, offset:int) returns (bool) { (isPtr && word(val) && gcAddrEx(val) && Pointer(rev, val - 4 - offset, abs) && !word(abs) && 0 <= offset && offset <= 4 * numFields(abs) - 4) || (isPtr && word(val) && !gcAddrEx(val) && abs == val) || (!isPtr && word(val) && abs == val) } function Value(isPtr:bool, rev:[int]int, val:int, abs:int) returns (bool) { InteriorValue(isPtr, rev, val, abs, 0) } ////////////////////////////////////////////////////////////////////////////// // BARTOK INTERFACE ////////////////////////////////////////////////////////////////////////////// function getBit(x:int, i:int) returns(bool) { 1 == and(shr(x, i), 1) } function getNib(x:int, i:int) returns(int) { and(shr(x, i), 15) } const ?SPARSE_TAG:int; axiom ?SPARSE_TAG == 1; const ?DENSE_TAG:int; axiom ?DENSE_TAG == 3; const ?PTR_VECTOR_TAG:int; axiom ?PTR_VECTOR_TAG == 5; const ?OTHER_VECTOR_TAG:int; axiom ?OTHER_VECTOR_TAG == 7; const ?PTR_ARRAY_TAG:int; axiom ?PTR_ARRAY_TAG == 9; const ?OTHER_ARRAY_TAG:int; axiom ?OTHER_ARRAY_TAG == 11; const ?STRING_TAG:int; axiom ?STRING_TAG == 13; function isOtherTag(t:int) returns(bool) { !( t == ?SPARSE_TAG || t == ?DENSE_TAG || t == ?PTR_VECTOR_TAG || t == ?OTHER_VECTOR_TAG || t == ?PTR_ARRAY_TAG || t == ?OTHER_ARRAY_TAG || t == ?STRING_TAG ) } function isVarSize(t:int) returns(bool) { t == ?PTR_VECTOR_TAG || t == ?OTHER_VECTOR_TAG || t == ?PTR_ARRAY_TAG || t == ?OTHER_ARRAY_TAG || t == ?STRING_TAG } function isReadonlyField(t:int, j:int) returns(bool) { (j == 1) || (t == ?PTR_VECTOR_TAG && j == 2) || (t == ?OTHER_VECTOR_TAG && j == 2) || (t == ?PTR_ARRAY_TAG && (j == 2 || j == 3)) || (t == ?OTHER_ARRAY_TAG && (j == 2 || j == 3)) || (t == ?STRING_TAG && j == 2) } const ?STRING_VTABLE:int; const ?VT_MASK:int; axiom ?VT_MASK == 60; const ?VT_BASE_LENGTH:int; axiom ?VT_BASE_LENGTH == 52; const ?VT_ARRAY_ELEMENT_SIZE:int; axiom ?VT_ARRAY_ELEMENT_SIZE == 44; const ?VT_ARRAY_ELEMENT_CLASS:int; axiom ?VT_ARRAY_ELEMENT_CLASS == 40; const ?VT_ARRAY_OF:int; axiom ?VT_ARRAY_OF == 36; function mask(vt:int) returns(int) { ro32(vt + ?VT_MASK) } function tag(vt:int) returns(int) { and(mask(vt), 15) } function baseLength(vt:int) returns(int) { ro32(vt + ?VT_BASE_LENGTH) } function arrayElementSize(vt:int) returns(int) { ro32(vt + ?VT_ARRAY_ELEMENT_SIZE) } function arrayElementClass(vt:int) returns(int) { ro32(vt + ?VT_ARRAY_ELEMENT_CLASS) } function arrayOf(vt:int) returns(int) { ro32(vt + ?VT_ARRAY_OF) } function baseWords(vt:int) returns(int) { shr(baseLength(vt), 2) } function arrayElementWords(vt:int) returns(int) { shr(arrayElementSize(vt), 2) } const ?TYPE_STRUCT:int; axiom ?TYPE_STRUCT == 3; // true ==> field j is pointer // false ==> field j is primitive function VFieldPtr(abs:int, f:int) returns(bool); function fieldToSlot(vt:int, j:int) returns(k:int); // field 0 is the first non-header field function Sparse1(abs:int, vt:int, j:int, field:int) returns(bool) { VFieldPtr(abs, j) == (fieldToSlot(vt, field) != 0) && (fieldToSlot(vt, field) != 0 ==> between(1, 8, fieldToSlot(vt, field)) && getNib(mask(vt), 4 * fieldToSlot(vt, field)) - 1 == field) } function Sparse2(vt:int, nFields:int) returns(bool) { (forall k:int::{TSlot(k)} TSlot(k) ==> 1 <= k && k < 8 && getNib(mask(vt), 4 * k) != 0 ==> between(0, nFields, getNib(mask(vt), 4 * k) - 1) && fieldToSlot(vt, getNib(mask(vt), 4 * k) - 1) == k) } function{:expand false} TVT(abs:int, vt:int) returns(bool) { true } function VTable(abs:int, vt:int) returns(bool); axiom (forall abs:int, vt:int::{TVT(abs, vt)} VTable(abs, vt) <==> ( !VFieldPtr(abs, 0) // REVIEW: is this redundant? && !VFieldPtr(abs, 1) // REVIEW: is this redundant? && (tag(vt) == ?DENSE_TAG ==> (forall j:int::{TO(j)} TO(j) ==> 2 <= j && j < numFields(abs) ==> VFieldPtr(abs, j) == (j < 30 && getBit(mask(vt), 2 + j))) // REVIEW: is the "j < 30" redundant? ) && (tag(vt) == ?SPARSE_TAG ==> (forall j:int::{TO(j)} TO(j) ==> Sparse1(abs, vt, j, j - 2)) && Sparse2(vt, numFields(abs) - 2) ) && (tag(vt) == ?STRING_TAG ==> (forall j:int::{TO(j)} TO(j) ==> !VFieldPtr(abs, j)) ) && (tag(vt) == ?PTR_VECTOR_TAG ==> between(3, numFields(abs), baseWords(vt)) && (forall j:int::{TO(j)} TO(j) ==> (baseWords(vt) <= j && j < numFields(abs) <==> VFieldPtr(abs, j))) ) && (tag(vt) == ?OTHER_VECTOR_TAG ==> !VFieldPtr(abs, 2) && inRo(arrayElementClass(vt) + ?VT_MASK, 4) // REVIEW && between(3, numFields(abs), baseWords(vt)) && (arrayOf(vt) != ?TYPE_STRUCT ==> (forall j:int::{TO(j)} TO(j) ==> !VFieldPtr(abs, j))) && (arrayOf(vt) == ?TYPE_STRUCT ==> !isVarSize(tag(arrayElementClass(vt)))) && (arrayOf(vt) == ?TYPE_STRUCT && mask(arrayElementClass(vt)) == ?SPARSE_TAG ==> (forall j:int::{TO(j)} TO(j) ==> !VFieldPtr(abs, j))) // REVIEW: redundant && (arrayOf(vt) == ?TYPE_STRUCT && tag(arrayElementClass(vt)) == ?SPARSE_TAG ==> (forall j:int::{TO(j)} TO(j) ==> (VFieldPtr(abs, j) ==> baseWords(vt) <= j && j < numFields(abs)) && (baseWords(vt) <= j && j < numFields(abs) ==> (forall m:int::{TO(m)} TO(m) ==> between(0, arrayElementWords(vt), j - Mult(arrayElementWords(vt), m) - baseWords(vt)) ==> baseWords(vt) + Mult(arrayElementWords(vt), m) + arrayElementWords(vt) <= numFields(abs) && Sparse1(abs, arrayElementClass(vt), j, j - Mult(arrayElementWords(vt), m) - baseWords(vt))))) && arrayElementWords(vt) >= 0 && Sparse2(arrayElementClass(vt), arrayElementWords(vt))) ) && (tag(vt) == ?PTR_ARRAY_TAG ==> between(4, numFields(abs), baseWords(vt)) && (forall j:int::{TO(j)} TO(j) ==> (baseWords(vt) <= j && j < numFields(abs) <==> VFieldPtr(abs, j))) ) && (tag(vt) == ?OTHER_ARRAY_TAG ==> !VFieldPtr(abs, 2) && !VFieldPtr(abs, 3) && between(4, numFields(abs), baseWords(vt)) && (arrayOf(vt) != ?TYPE_STRUCT ==> (forall j:int::{TO(j)} TO(j) ==> !VFieldPtr(abs, j))) ) && (isOtherTag(tag(vt)) ==> (forall j:int::{TO(j)} TO(j) ==> VFieldPtr(abs, j) == (fieldToSlot(vt, j) != 0) && (fieldToSlot(vt, j) != 0 ==> between(1, 1 + ro32(mask(vt)), fieldToSlot(vt, j)) && ro32(mask(vt) + 4 * fieldToSlot(vt, j)) + 1 == j)) && (forall k:int::{TSlot(k)} TSlot(k) ==> 1 <= k && k < 1 + ro32(mask(vt)) ==> inRo(mask(vt) + 4 * k, 4) && (ro32(mask(vt) + 4 * k) != 0 ==> between(0, numFields(abs), ro32(mask(vt) + 4 * k) + 1) && fieldToSlot(vt, ro32(mask(vt) + 4 * k) + 1) == k)) && inRo(mask(vt), 4) && ro32(mask(vt)) >= 0 ) )); axiom (forall abs:int, vt:int::{TVT(abs, vt)} VTable(abs, vt) ==> inRo(vt + ?VT_MASK, 4) && inRo(vt + ?VT_BASE_LENGTH, 4) && inRo(vt + ?VT_ARRAY_ELEMENT_SIZE, 4) && inRo(vt + ?VT_ARRAY_ELEMENT_CLASS, 4) && inRo(vt + ?VT_ARRAY_OF, 4) ); function pad(i:int) returns(int) { and(i + 3, neg(3)) } function{:expand false} TVL(abs:int) returns(bool) { true } function ObjSize(abs:int, vt:int, nElems1:int, nElems2:int) returns(bool); axiom (forall abs:int, vt:int, nElems1:int, nElems2:int::{TVL(abs), ObjSize(abs, vt, nElems1, nElems2)} ObjSize(abs, vt, nElems1, nElems2) <==> ( numFields(abs) >= 2 && (!isVarSize(tag(vt)) ==> 4 * numFields(abs) == baseLength(vt)) && (tag(vt) == ?STRING_TAG ==> numFields(abs) >= 4 && 4 * numFields(abs) == pad(16 + 2 * nElems1)) && (tag(vt) == ?PTR_VECTOR_TAG ==> numFields(abs) >= 3 && 4 * numFields(abs) == pad(baseLength(vt) + 4 * nElems1)) && (tag(vt) == ?OTHER_VECTOR_TAG ==> numFields(abs) >= 3 && 4 * numFields(abs) == pad(baseLength(vt) + Mult(arrayElementSize(vt), nElems1))) && (tag(vt) == ?PTR_ARRAY_TAG ==> numFields(abs) >= 4 && 4 * numFields(abs) == pad(baseLength(vt) + 4 * nElems2)) && (tag(vt) == ?OTHER_ARRAY_TAG ==> numFields(abs) >= 4 && 4 * numFields(abs) == pad(baseLength(vt) + Mult(arrayElementSize(vt), nElems2))) )); type $FrameLayout; function frameLayoutArgs($FrameLayout) returns(int); function frameLayoutLocals($FrameLayout) returns(int); function frameHasPtr($FrameLayout, int) returns(bool); function frameDescriptor($FrameLayout) returns(desc:int); function frameFieldToSlot($FrameLayout, int) returns(int); var $FrameCount:int; var $FrameSlice:[int]int; var $FrameAddr:[int]int; var $FrameLayout:[int]$FrameLayout; var $FrameMem:[int]int; var $FrameAbs:[int][int]int; var $FrameOffset:[int]int; function inFrame(layout:$FrameLayout, j:int) returns(bool) { -frameLayoutLocals(layout) <= j && j < 2 + frameLayoutArgs(layout) } function{:expand false} TVF(l:$FrameLayout) returns(bool) { true } axiom (forall l:$FrameLayout, j:int::{TVF(l),TO(j)} (inFrame(l, 0) && !frameHasPtr(l, 0)) && (inFrame(l, 1) && !frameHasPtr(l, 1)) && (TO(j) && frameHasPtr(l, j) ==> inFrame(l, j)) && (TO(j) && getBit(frameDescriptor(l), 0) && !getBit(frameDescriptor(l), 1) && and(shr(frameDescriptor(l), 6), 1023) == 0 ==> between(0, 16, frameLayoutArgs(l)) && frameLayoutArgs(l) == and(shr(frameDescriptor(l), 2), 15) && (frameHasPtr(l, j) ==> 0 <= frameLayoutArgs(l) + 1 - j && frameLayoutArgs(l) - 1 - j < 16) && (0 <= frameLayoutArgs(l) + 1 - j && frameLayoutArgs(l) - 1 - j < 16 ==> ( (j >= 2 ==> frameHasPtr(l, j) == getBit(frameDescriptor(l), 16 + frameLayoutArgs(l) + 1 - j)) && (j < 0 ==> frameHasPtr(l, j) == getBit(frameDescriptor(l), 16 + frameLayoutArgs(l) - 1 - j))) )) && (TO(j) && !getBit(frameDescriptor(l), 0) ==> inRo(frameDescriptor(l), 4)) && (TO(j) && !getBit(frameDescriptor(l), 0) && ro32(frameDescriptor(l)) == 4096 && inFrame(l, j) ==> ( inRo(frameDescriptor(l) + 4, 1) && inRo(frameDescriptor(l) + 6 + frameFieldToSlot(l, j), 1) && j == roS8(frameDescriptor(l) + 6 + frameFieldToSlot(l, j)) && frameHasPtr(l, j) == ( between(0, roU8(frameDescriptor(l) + 4), frameFieldToSlot(l, j)) ) ))); axiom (forall l:$FrameLayout, k:int::{TVF(l),TSlot(k)} TSlot(k) && !getBit(frameDescriptor(l), 0) && ro32(frameDescriptor(l)) == 4096 && between(0, roU8(frameDescriptor(l) + 4), k) ==> inFrame(l, roS8(frameDescriptor(l) + 6 + k)) && k == frameFieldToSlot(l, roS8(frameDescriptor(l) + 6 + k)) ); const ?sectionCount:int; const ?staticDataPointerBitMap:int; const ?dataSectionEnd:int; const ?dataSectionBase:int; function sectionSlice(ptr:int) returns(section:int); function sectionBase(section:int) returns(ptr:int) { ro32(?dataSectionBase + 4 * section) } function sectionEnd(section:int) returns(ptr:int) { ro32(?dataSectionEnd + 4 * section) } function sectionHasPtr(section:int, j:int) returns(bool); function inSectionData(ptr:int) returns(bool); function{:expand false} TVS(s:int, j:int) returns(bool) { true } axiom (forall s:int, j:int::{TVS(s, j)} 0 <= s && s < ?sectionCount && 0 <= j ==> inRo(?dataSectionBase + 4 * s, 4) && inRo(?dataSectionEnd + 4 * s, 4) && inRo(?staticDataPointerBitMap + 4 * s, 4) && (sectionBase(s) + 4 * j < sectionEnd(s) ==> inSectionData(ro32(?dataSectionBase + 4 * s) + 4 * j) && inRo(ro32(?staticDataPointerBitMap + 4 * s) + 4 * shr(j, 5), 4) && and(j, 31) < 32 // REVIEW: this is true, so just prove it && sectionHasPtr(s, j) == getBit( ro32(ro32(?staticDataPointerBitMap + 4 * s) + 4 * shr(j, 5)), and(j, 31) ))); const ?callSiteTableCount:int; const ?codeBaseStartTable:int; const ?returnAddressToCallSiteSetNumbers:int; const ?callSiteSetCount:int; const ?callSiteSetNumberToIndex:int; const ?activationDescriptorTable:int; function LookupDesc(t:int, j:int) returns(int) { ro32(ro32(?activationDescriptorTable + 4 * t) + 4 * roU16(ro32(?callSiteSetNumberToIndex + 4 * t) + 2 * j)) } function InTable(t:int, j:int) returns(bool) { 0 <= t && t < ?callSiteTableCount && 0 <= j && j < ro32(ro32(?callSiteSetCount + 4 * t)) } function RetAddrAt(t:int, j:int, retaddr:int) returns(bool) { InTable(t, j) && between(ro32(ro32(?returnAddressToCallSiteSetNumbers + 4 * t) + 4 * j), ro32(ro32(?returnAddressToCallSiteSetNumbers + 4 * t) + 4 * (j + 1)), retaddr - ro32(?codeBaseStartTable + 4 * t)) } function{:expand false} TVFT(f:int) returns(bool) { true } function FrameNextInv(f:int, ra:int, nextFp:int, $FrameAddr:[int]int, $FrameLayout:[int]$FrameLayout) returns(bool); axiom (forall f:int, ra:int, nextFp:int, $FrameAddr:[int]int, $FrameLayout:[int]$FrameLayout::{TVFT(f), FrameNextInv(f, ra, nextFp, $FrameAddr, $FrameLayout)} FrameNextInv(f, ra, nextFp, $FrameAddr, $FrameLayout) <==> ( f >= 0 && (f == 0 <==> !(exists t:int, j:int::{TT(t), TO(j)} TT(t) && TO(j) && RetAddrAt(t, j, ra))) && (f > 0 ==> $FrameAddr[f - 1] == nextFp && (forall t:int, j:int::{TT(t), TO(j)} TT(t) && TO(j) ==> RetAddrAt(t, j, ra) ==> frameDescriptor($FrameLayout[f - 1]) == LookupDesc(t, j))) )); axiom (forall f:int::{TVFT(f)} ( (forall t:int::{TT(t)} TT(t) ==> 0 <= t && t < ?callSiteTableCount ==> inRo(?codeBaseStartTable + 4 * t, 4) && inRo(?returnAddressToCallSiteSetNumbers + 4 * t, 4) && inRo(?callSiteSetCount + 4 * t, 4) && inRo(ro32(?callSiteSetCount + 4 * t), 4) && inRo(?callSiteSetNumberToIndex + 4 * t, 4) && inRo(?activationDescriptorTable + 4 * t, 4) && (forall j:int::{TO(j)} TO(j) ==> 0 <= j && j <= ro32(ro32(?callSiteSetCount + 4 * t)) ==> inRo(ro32(?returnAddressToCallSiteSetNumbers + 4 * t) + 4 * j, 4) && inRo(ro32(?callSiteSetNumberToIndex + 4 * t) + 2 * j, 2) && inRo(ro32(?activationDescriptorTable + 4 * t) + 4 * roU16(ro32(?callSiteSetNumberToIndex + 4 * t) + 2 * j), 4) ) ) )); axiom (forall f:int::{TVFT(f)} ( (forall t:int, j1:int, j2:int::{TT(t), TO(j1), TO(j2)} TT(t) && TO(j1) && TO(j2) ==> 0 <= t && t < ?callSiteTableCount && 0 <= j1 && j1 < j2 && j2 <= ro32(ro32(?callSiteSetCount + 4 * t)) ==> ro32(ro32(?returnAddressToCallSiteSetNumbers + 4 * t) + 4 * j1) < ro32(ro32(?returnAddressToCallSiteSetNumbers + 4 * t) + 4 * j2) ) )); // Note: we allow reads, but not writes, from $frame == $FrameCount, which // is the garbage collector's own initial stack frame. This feature allows // the collector to read the arguments and saved return address set // by the mutator. It does not allow reading the collector's own data. procedure FrameLoad($frame:int, ptr:int) returns(val:int); requires 0 <= $frame && $frame <= $FrameCount; requires $FrameSlice[ptr] == $frame; ensures word(val); ensures val == $FrameMem[ptr]; procedure FrameStore($frame:int, ptr:int, val:int); requires 0 <= $frame && $frame < $FrameCount; requires $FrameSlice[ptr] == $frame; requires word(val); modifies $FrameMem; ensures $FrameMem == old($FrameMem)[ptr := val]; function FrameInv(f:int, l:$FrameLayout, r:[int]int, $FrameAddr:[int]int, $FrameSlice:[int]int, $FrameLayout:[int]$FrameLayout, $FrameMem:[int]int, $FrameAbs:[int][int]int, $FrameOffset:[int]int, $Time:Time) returns(bool) { FrameNextInv(f, $FrameMem[$FrameAddr[f] + 4], $FrameMem[$FrameAddr[f]], $FrameAddr, $FrameLayout) && (forall j:int::{TO(j)} TO(j) ==> inFrame(l, j) ==> $FrameSlice[$FrameAddr[f] + 4 * j] == f && word($FrameAddr[f] + 4 * j) && InteriorValue(frameHasPtr(l, j), r, $FrameMem[$FrameAddr[f] + 4 * j], $FrameAbs[f][j], $FrameOffset[$FrameAddr[f] + 4 * j]) && (frameHasPtr(l, j) && gcAddrEx($FrameMem[$FrameAddr[f] + 4 * j]) ==> reached($FrameAbs[f][j], $Time)) ) } function StackInv(r:[int]int, $FrameCount:int, $FrameAddr:[int]int, $FrameLayout:[int]$FrameLayout, $FrameSlice:[int]int, $FrameMem:[int]int, $FrameAbs:[int][int]int, $FrameOffset:[int]int, t:Time) returns(bool) { (forall f:int::{TV(f)} TV(f) ==> 0 <= f && f < $FrameCount ==> FrameInv(f, $FrameLayout[f], r, $FrameAddr, $FrameSlice, $FrameLayout, $FrameMem, $FrameAbs, $FrameOffset, t)) } var $SectionMem:[int]int; var $SectionAbs:[int][int]int; function SectionInv(s:int, i1:int, i2:int, r:[int]int, $SectionMem:[int]int, $SectionAbs:[int][int]int, t:Time) returns(bool) { (forall j:int::{TO(j)} TO(j) ==> 0 <= j && i1 + 4 * j < i2 ==> sectionSlice(i1 + 4 * j) == s && Value(sectionHasPtr(s, j), r, $SectionMem[i1 + 4 * j], $SectionAbs[s][j]) && (sectionHasPtr(s, j) && gcAddrEx($SectionMem[i1 + 4 * j]) ==> reached($SectionAbs[s][j], t)) ) } function StaticInv(r:[int]int, $SectionMem:[int]int, $SectionAbs:[int][int]int, t:Time) returns(bool) { (forall s:int::{TV(s)} TV(s) ==> 0 <= s && s < ?sectionCount ==> SectionInv(s, sectionBase(s), sectionEnd(s), r, $SectionMem, $SectionAbs, t)) } procedure SectionLoad(ptr:int) returns(val:int); requires inSectionData(ptr); ensures word(val); ensures val == $SectionMem[ptr]; procedure SectionStore(ptr:int, val:int); requires inSectionData(ptr); requires word(val); modifies $SectionMem; ensures $SectionMem == old($SectionMem)[ptr := val]; ////////////////////////////////////////////////////////////////////////////// // ABSTRACT AND CONCRETE GRAPHS ////////////////////////////////////////////////////////////////////////////// // Each integer i is considered a concrete node. // The memory manager controls concrete nodes in memory. // - Each abstract object node is either mapped to one concrete node or is unmapped // - Each concrete node is either mapped to one abstract object node or is unmapped // - If abstract object node A is mapped to concrete node C, then C is mapped to A // Let the notation C<-->A indicate that A is mapped to C and C is mapped to A. // Let the notation C-->none indicate that C is unmapped. // Let the notation A-->none indicate that A is unmapped. // The variable $toAbs maps concrete nodes to abstract nodes, and thereby // exactly describes all C<-->A and C-->none. The A-->none mappings are // implied; if there is no C such that C<-->A, then A-->none. var $toAbs:[int]int; // maps a concrete node C to an abstract node A or to "none" // MAP_NO_ABS has no C<-->A mappings. const MAP_NO_ABS:[int]int; axiom (forall i:int::{TV(i)} TV(i) ==> MAP_NO_ABS[i] == NO_ABS); // WellFormed($toAbs) ensures that if C1 != C2 and $toAbs[C1] != NO_ABS // and $toAbs[C2] != NO_ABS then $toAbs[C1] != $toAbs[C2]. function WellFormed($toAbs:[int]int) returns(bool) { (forall i1:int, i2:int::{TV(i1), TV(i2)} TV(i1) && TV(i2) && gcAddr(i1) && gcAddr(i2) && i1 != i2 && $toAbs[i1] != NO_ABS && $toAbs[i2] != NO_ABS ==> $toAbs[i1] != $toAbs[i2]) }