RustyDL: A Program Logic for Rust

Rust is a modern programming language that guarantees memory safety and the absence of data races with a strong type system. We present RustyDL, a program logic for Rust, as a foundation for an auto-interactive, deductive verification tool for Rust. …

Authors: Daniel Drodt, Reiner Hähnle

RustyDL: A Program Logic for Rust Daniel Dro dt(  ) [0000 − 0003 − 3036 − 8220] and Reiner Hähnle [0000 − 0001 − 8000 − 7613] T ec hnisc he Univ ersität Darmstadt, Darmstadt, German y {daniel.drodt,reiner.haehnle}@tu-darmstadt.de Abstract. Rust is a mo dern programming language that guaran tees memory safet y and the absence of data races with a strong t ype system. W e present RustyDL, a program logic for Rust, as a foundation for an auto-in teractiv e, deductive verification tool for Rust. RustyDL reasons ab out Rust programs directly on the source co de level, in contrast to other tools that are all based on translation to an intermediate language. A source-level program logic for Rust is crucial for a human-in-the-loop (HIL) st yle of v erification that p ermits pro ving highly complex functional prop erties. W e discuss sp ecific Rust challenges in designing a program logic and calculus for HIL-st yle verification and prop ose a solution in eac h case. W e provide a proof-of-concept of our ideas in the form of a protot yp e of a Rust instance of the w ell-kno wn d eductiv e verification to ol Ke Y. Keyw ords: Deductiv e v erification · Program logic · Rust verification 1 In tro duction Rust is a mo dern programming language with a strong t ype system, guaranteeing memory safety without garbage collection. F or this, it relies on a t ype system for managing ownership , references, and b orrowing, which prev en ts data races. Rust b ecame highly p opular and started to b eing used in the Linux kernel [24,32]. In suc h safety-critical use cases, the correctness of programs is paramount, so formal v erification plays an imp ortant role. Ov er the last years, a v ariety of to ols for formally verifying Rust co de ha v e b een proposed. The general approach of most existing tools [4,9,12,22] is to translate Rust co de and formal specifications in to an in termediate language, suc h as Vip er [26] or Wh y3 [15], whic h is supported b y existing v erification to ols. This intermediate co de is then used to generate first-order verification conditions to b e disc harged with SMT solvers. This translation-based approach p ermits to implement a deductiv e verifica- tion to ol with relative ease, b ecause it is based on an existing to ol chain. It also offers a high degree of automation. The do wnsides of the arc hitecture are: first, one has to trust the (highly non-trivial) translation from Rust and its sp ecifica- tion to an intermediate language; second, it is challenging to relate the output of an SMT solver to the co de and sp ecification under v erification, in case a ver- ification attempt fails: a verification engineer cannot directly interact with the pro of or apply pro of steps man ually . 2 D. Dro dt and R. Hähnle In the sp ectrum b etw een full automation of pro ofs and full con trol ov er the v erification pro cess, existing Rust v erification to ols are trending to w ards the former. At the other end, verification to ols such as Ke Y [7] or KIV [30] pro- vide explicit pro of ob jects and allow their users to insp ect and apply any pro of step, while retaining p ow erful automation capabilities. This is achiev ed with a program logic at the sour c e c o de level, typically axiomatized in a sequent cal- culus. Such a “human-in-the-loop” (HIL), source code-level approac h permits fine-grained con trol. Consequently , it was p ossible to verify (and find bugs in) highly complex, unaltered Ja v a source co de libraries [8,11]. This could so far not b e replicated with systems based on translation to an intermediate language. Righ t no w, there is no HIL, source code-level deductive verifier for Rust. Therefore, a natural researc h question arises that we answer in the presen t pap er: Can one transfer HIL, source co de verification to the w orld of Rust? What are the necessary conditions? How can w e model Rust’s type system and b ehavior, including o wnership and references, in a source co de program logic? Accordingly , our main result is the program logic RustyDL for HIL, source co de verification. W e show that a Dynamic Logic with a few familiar extensions is a natural fit for Rust verification. As a pro of-of-concept, we also provide a prototype of an implemen tation based on the Ke Y v erifier, called R usty Ke Y . Section 2 pro vides bac kground on Rust and in tro duces our program logic for Rust. In Section 3, we define the sequent calculus for RustyDL. Section 4 compares our approac h with related work. W e conclude the pap er in Section 5. 2 The Rusty Program Logic Rust is a complex language with a rich t yp e system and support for concurrency . It is divided in to t wo sub-languages: safe Rust limits the dev elop er but pro vides strong memory safety guaran tee s, while unsafe R ust has weak er guarantees but offers more con trol. The latter must b e marked with the keyw ord unsafe . W e fo cus on safe Rust b ecause: (1) most Rust functions are safe; (2) modeling safe Rust pro vides opp ortunities (and challenges) for verification: mec hanisms suc h as separation logic or dynamic frames are unnecessary in this setting; and (3) it can b e assumed that unsafe Rust is encapsulated, i.e., it is confined to a part of the co de that ma y b e analyzed and verified separately [5]. F urther, w e fo cus here on a subset of Rust, mainly primitive v alues, shared and mutable references, and arrays. Structs and en ums can b e mo deled similarly to arrays or references. W e leav e unions, smart p ointers, and traits for future work. 2.1 The Rust Programming Language W e briefly review those features of Rust necessary to follo w this pap er. F or details and the entiret y of Rust, we refer to [21,28]. Rust applications and libraries are organized in units called cr ates , whic h can dep end on other crates. Each crate is split in to mo dules containing functions, type definitions, and implementations. RustyDL: A Program Logic for Rust 3 Listing 1. Shared and mutable references in Rust 1 let n: u32 = 1; 2 let x: & u32 = &n; 3 p rintln! ( "{}" , ∗ x); // prints "1" 4 5 fn increase_lo wer(a: & mut u64 , b: & mut u64 , amount: u64 ) { 6 let lo wer = if a < b { a } else { b }; 7 ∗ lo wer = ∗ low er + amount; 8 } The Rust types imp ortant for this pap er are b o ol , the in teger types i8 ,. . . , i128 for signed integers, u8 ,. . . , u128 for unsigned in tegers, the empty tuple t yp e () , and array types such as [ i16 ; 10] . Rust arra ys are of fixed length (here: 10 ) and their length is part of the t yp e. Rust’s unique feature is the t yp e system’s enforcemen t of ownership , and r efer enc es . Ownership means eac h v alue is owne d by exactly one owner (e.g., a v ariable or an arra y field). The v alue is remov ed from memory , or dr opp e d , once the owner go es out of scope. This feature is the basis of Rust’s memory safety . Ownership of a v alue is tr ansferr e d on assignmen t—one says the v alue is move d . Example 1. F unction increase_lo wer in Listing 1 tak es t wo mutable references a , b as arguments, compares their v alues, stores the low er reference in low er , and increases that v alue by amount . Observe that in Rust, control structures such as conditionals, lo ops, and blo cks are expressions. The assignment in line 6 mov es either a or b to low er . Hence, one of a or b is no longer initialize d , while low er no w con tains the mo ved v alue. Reading from a or b after line 6 is a compile-time error. This ensures that only initialized memory is read. T yp es with a small memory fo otprint, e.g., integer types, do not follow the mo ve semantics. Their v alue is c opie d rather than mov ed on assignment. In Rust terminology: They implement the Copy trait. The only types in this pap er not implemen ting this trait are mutable refe rences and arrays of mutable references. Since accessing a v alue only through one owner is, in general, to o limiting, Rust provides references. Shar e d (or immutable) r efer enc es ( & ) permit read-only access to the b orr owe d v alue, while mutable r efer enc es ( & mut ) permit read-and- write access. As seen in the first three lines of Listing 1, a shared reference of t yp e u32 is of type & u32 . The b orrow ed v alue 1 can b e read from reference x using the der efer encing op erator ∗ x . While the b orrow x is live, w e say n is b orrow ed. In case of a mutable reference, the b orrow ed v alue cannot merely b e read but also mo dified using the dereferencing op erator, see the second to last line of the example. The Rust compiler—more sp ecifically , its b orr ow che cker —ensures programs follo w certain rules, whic h imply the absence of data races: (1) b orrow ed o wners are not written to directly; (2) mutably borrow ed o wne rs are only read through 4 D. Dro dt and R. Hähnle Any b o ol int unit arra y sorts Field Plac e reference sorts ! Fig. 1. The sort hierarc hy of RustyDL the reference; (3) at any p oint in the program, eac h owner is b orrow ed either mu- tably once or imm utably arbitrarily often; (4) b orrow ed v alues are not dropp ed. References in Rust ha ve an asso ciated lifetime whic h is necessary in complex programs. W e do not consider lifetimes in our logic and omit them here. In the following, we not only consider en tire crates but also fr agments of Rust programs. Definition 1 defines the set of legal program fragments, i.e., the Rust fragmen ts our logic can deal with. Definition 1 (Legal program fragmen t). L et Crt b e a valid, i.e., c ompiler appr ove d, Rust cr ate. A legal program fragment is a se quenc e of Rust state- ments p in Crt (and an optional Rust expr ession at its end), with lo c al variables a 1 , . . . , a n of Rust typ es T 1 , . . . , T n such that inserting the function fn f( mut a 1 : T 1 , . . . , mut a n : T n ) { { p }; } into the entry p oint of Crt yields a le gal pr o gr am ac c or ding to the Rust c ompiler. The fragmen t p is wrapp ed in a blo ck expression. W e add the semicolon to ensure that the function returns nothing, simplifying t yp e chec king. 2.2 Rust y Dynamic Logic Dynamic lo gic is a program logic suggested in [27] and b eing highly suitable for program verification [2]. W e present a version of dynamic logic tailored to Rust called RustyDL, which is an extension of typed first-order logic. Our definitions are based on those for Ja v aDL, a dynamic logic for Jav a, as presented in [1]. Figure 1 shows RustyDL’s sort hierarch y , i.e., the hierarch y of logical types. A ny is the sup er-sort of all sorts, while the “nev er sort” ! is the bottom sort no term can ha ve. The type b o ol is mapp ed to b o ol , the unit type () to unit . Signed and unsigned integer types are mapp ed to mathematical in tegers int (see Section 3.4 for ho w w e ac hieve an accurate model). F or each array type [T;N] we create an arra y sort [ T ; N ] . F urther, for eac h sort A , we define a shared reference sort Ref s A and a mutable reference sort Ref m A . Rust types &T and & mut T map to the reference sorts in the exp ected w ay . W e demand of the signature that it contains the 0 -ary predicates true and false , the binary predicate . = ( Any , Any ) for equality , all usual integer functions and predicates suc h as + , − , · , > , and the constants TR UE , F ALSE of sort b o ol . RustyDL: A Program Logic for Rust 5 W e use b oth true and TRUE to differen tiate b etw een the tautology and the true b o olean v alue, as the former is a form ula and the latter a term; hence, they may app ear in differen t scenarios. Analogous for false and F ALSE . RustyDL extends the set of classical first-order logic terms with pr o gr am vari- ables , e.g., a : int , where int is a ’s sort. These are similar to constants, but their v alue is state-dep endent. Unlike first-order logical v ariables, program v ariables ma y app ear in programs, but one cannot quantify ov er program v ariables. W e extend the set of first-order form ulas with the modal operators ⟨ p ⟩ ϕ (“diamond”) and [ p ] ϕ (“b ox”), for program fragment p and form ula ϕ . Intuitiv ely , for deterministic programs, ⟨ p ⟩ ϕ expresses that p terminates, and, in the state resulting from this execution, ϕ holds, i.e. total correctness. Conv ersely , [ p ] ϕ holds iff p do es not terminate, or ϕ holds in the resulting state, i.e. partial correctness. A Kripke structur e K = ( D , I , S , ρ ) ov er domain D and in terpretation I formalizes the state c hanges expressed b y the mo dality formulas, ordering a set of states S according to p ossible state transitions ρ through programs. States in S map program v ariables to elements in D . I , as in FOL, ev aluates functions and relations. F or a program fragment p , ρ ( p ) is a partial function from S to S that defines the state c hange caused by p . This c hange is only defined if p terminates normally , see Definition 2. Definition 2 (RustyDL Kripke structure). L et Crt b e a valid R ust cr ate. A RustyDL Kripke structure for a signatur e derive d fr om Crt is a p air K = ( S , ρ ) wher e – S is an infinite set of first-or der structur es s = ( D , I ) , c al le d states , close d under the fol lowing pr op erty: al l states c oincide in their domain D and in- terpr etation I of function and pr e dic ate symb ols. – ρ is a function mapping a le gal pr o gr am fr agment p to a p artial function S ⇀ S with ρ ( p )( s 1 ) = s 2 iff evaluating p in state s 1 terminates normal ly in s 2 . If p do es not terminate normal ly starting in s 1 , ρ ( p )( s 1 ) is undefine d. Note that we assume R ust pr o gr ams to b e deterministic. F or brevity , the transition function ρ is left undefined, as it dep ends on a formal semantics for Rust. Our calculus rules provide an axiomatic semantics of ρ . Definition 3 gives the semantics of terms and formulas, including program v ariables and mo dalities, relative to a Kripke structure K . Definition 3 (Semantics of RustyDL terms and form ulas). L et Crt b e a valid Rust cr ate, VSym the set of lo gic variable symb ols, K = ( D , I , S , ρ ) a Kripke structur e mo deling Rust pr o gr ams, s ∈ S a state, and β : VSym → D a variable assignment. F or every RustyDL term t we define its evaluation inductively by the evalua- tion function val K ,s,β exactly as in first-or der lo gic, with evaluation of pr o gr am variables define d as val K ,s,β ( a ) = s ( a ) for e ach pr o gr am variable a . F or every R ustyDL formula ϕ we define satisfaction, denote d ( K , s, β ) | = ϕ , exactly as in first-or der lo gic with two new c ases: 6 D. Dro dt and R. Hähnle 1. ( K , s, β ) | = [ p ] ϕ iff ρ ( p )( s ) is undefine d or ρ ( p )( s ) = s ′ and ( K , s ′ , β ) | = ϕ , 2. ( K , s, β ) | = ⟨ p ⟩ ϕ iff ρ ( p )( s ) = s ′ exists and ( K , s ′ , β ) | = ϕ . In RustyDL one can express a large v ariety of prop erties of Rust co de. Partial correctness of program p w.r.t. pre- and p ost-conditions pr e , p ost is written as pr e → [ p ] p ost . This formula is equiv alen t to the Hoare triple { pr e } p { p ost } [18] and it is v alid iff for every state s such that pr e is satisfied in s , ev aluating pro- gram p in s either does not terminate or in the resulting state s ′ , p ost is satisfied. In contrast, pr e → ⟨ p ⟩ p ost expresses total correctness. Unlike Hoare logic, dy- namic logic is closed under all op erations. Hence, e.g., p ost , ma y contain further mo dalities p ermitting to express program equiv alence, refinement, etc. [2]. T o obtain a usable RustyDL calculus that can mo del state changes efficiently , w e provide a more fine-grained state transition mechanism than the mo dalities called up date . Definition 4 extends Jav aDL up dates [1] with the mutating up dates . Definition 4 (Up date, Up date Application). W e define the set Up d induc- tively by (the se c ond clause is new c omp ar e d to [1]): 1. ( a := t ) ∈ Up d for e ach pr o gr am variable a : A and e ach term t of sort A , 2. ( t 1 ∗ → t 2 ) ∈ Upd for e ach term t 1 of sort Ref m A and t 2 of sort A , 3. skip ∈ Up d , 4. ( u 1 ∥ u 2 ) ∈ Upd for al l up dates u 1 , u 2 ∈ Up d , 5. ( { u 1 } u 2 ) ∈ Upd for al l up dates u 1 , u 2 ∈ Up d . W e extend the inductive definition of terms with term { u } t of sort A for al l up dates u ∈ Upd and terms t of sort A . The inductive definition of formulas is extende d with formula { u } ϕ for al l up dates u ∈ Upd and formulas ϕ . Up dates are applie d syntactically to terms and form ulas. Update application c hanges the state wherein a term (formula) is ev aluated. Up dates allo w us to realize the decomposition of a large program fragmen t into a series of small, deterministic state c hanges (see Definition 5 b elow). Example 2. Let x , y b e program v ariables of type i32 and z of type & mut i32 . W e give examples of up dates with their intuitiv e meaning: – skip is an update expressing no state change. – An elementary up date ( x := x + 1) expresses a state change where program v ariable x is incremented b y one. The up date application { x := x + 1 } x is seman tically equiv alent to the expression x + 1 . – The p ar al lel up date ( x := y ∥ y := x ) expresses a state change where x and y sw ap v alues. Suc h parallel up dates express state changes with no interference among them. Multiple up dates to the same v ariable can o ccur in one parallel up date; then, the last one o verwrites the earlier ones. – ( z ∗ → y − 1) is a mutating update, expressing that the lo cation borrow ed by z is set to y − 1 . RustyDL: A Program Logic for Rust 7 Mutating up dates are crucial for representing mutable references in our logic. T o mo del the latter in RustyDL, we need to inv olve not merely v alues, but also the b orrow ed location (the lender ). T o this end, w e use the sort Plac e (the notion is taken from the Rust compiler [29]). F or each program v ariable x , we define a unique constan t T x W : Plac e . T o mo del the lo cation of an array field, we add a function arrPlc : Ref m [ A ; n ] × int → Plac e for eac h sort A and n ∈ N , intuitiv ely , mapping each field to its place. W e write T t, t ′ W for arrPlc ( t, t ′ ) . Thus, the term mr ef ( pl ) is of sort Ref m A , where pl : Plac e is the place of the lender of sort A . Ho w these mutable references are used is shown in Section 3.5. Example 3. Let x : int and a : [ int ; 4] be program v ariables. The mutable refer- ence mr ef ( T x W ) expresses that x is mutably b orrow ed and mr ef ( T mr ef ( T a W ) , 1 W ) expresses that the field at index 1 of arra y a is mutably b orrow ed. Mutating up dates are the central extension of our logic. They fit naturally in to our dynamic logic and allo w for handling m utable references with little conceptual ov erhead. Alternativ es, such as explicitly mo deling memory or the set of activ e loans, rapidly b ecome unwieldy and computationally complex when used in a logic. Definition 5 formalizes the semantics of updates and their applications that w as describ ed ab ov e intuitiv ely . Definition 5 (Semantics of RustyDL up dates). L et Crt b e a valid Rust cr ate, K = ( S , ρ ) a Kripke structur e for Crt , s ∈ S a state, and β : VSym → D a variable assignment. The valuation function val K ,s,β : Up d → ( S → S ) is define d as: val K ,s,β ( a := t )( s ′ )( b ) = ( val K ,s,β ( t ) if b = a s ′ ( b ) otherwise for al l s ′ ∈ S , b ∈ ProgVSym val K ,s,β ( t 1 ∗ → t 2 )( s ′ )( b ) = ( val K ,s,β ( t 2 ) if val K ,s,β ( t 1 ) = val K ,s,β ( mr ef ( T b W )) s ′ ( b ) otherwise for al l s ′ ∈ S , b ∈ ProgVSym val K ,s,β ( skip )( s ′ ) = s ′ for al l s ′ ∈ S val K ,s,β ( u 1 ∥ u 2 )( s ′ ) = val K ,s,β ( u 2 )( val K ,s,β ( u 1 )( s ′ )) for al l s ′ ∈ S val K ,s,β ( { u 1 } u 2 ) = val K ,s ′ ,β ( u 2 ) wher e s ′ = val K ,s,β ( u 1 )( s ) W e extend the inductive definition of the semantics of RustyDL terms and for- mulas (Definition 3) with val K ,s,β ( { u } t ) = val K ,s ′ ,β ( t ) wher e s ′ = val K ,s,β ( u )( s ) and ( K , s, β ) | = { u } ϕ iff ( K , s ′ , β ) | = ϕ wher e s ′ = val K ,s,β ( u )( s ) . 8 D. Dro dt and R. Hähnle Example 4. Let x b e a program v ariable of type i32 . Clearly , x . = 0 → x > 0 is unsatisfiable. How ever, applying the up date x := x + 1 to the right-hand side mak es the formula v alid: | = x . = 0 → { x := x + 1 } ( x > 0) . Compare the up date ab o ve with the following mo dality: x . = 0 → ⟨ x = x + 1; ⟩ ( x > 0) . It is instructive to observe the difference: Up dates are total state c hanges, they are guaranteed to terminate normally . Hence, they are similar to explicit substitutions. Mo dalities, on the other hand, may not terminate. Addition may lead to an o verflo w, causing exceptional termination: ρ ( p ) is partial. 3 RustyDL Calculus Recall that functional total correctness of a program fragment p , relative to a precondition pr e and p ostcondition p ost , is expressed by v alidity of the RustyDL form ula pr e → ⟨ p ⟩ p ost . This can b e pro ven in a sequent calculus for RustyDL: V alidity is shown by deriving the sequent “ = ⇒ pr e → ⟨ p ⟩ p ost ”. The calculus giv en b elo w is soun d: All rules in this section hav e b een pro ven sound relativ e to a formal semantics [14] in ongoing work. 3.1 Challenges in the Design of a Sequen t Calculus for RustyDL W e mo del Rust’s unique features and its semantics in terms of rules of a program logic, striving to retain a h uman-readable format to enable human in teraction. F or this, w e work on the only slightly simplified and normalized high-lev el in ter- mediate representation (HIR) used in the Rust compiler [29]. This representation is close to source-level Rust but with macros expanded and some structures like the differen t forms of lo op normalized. W e identify five core features of Rust our logic m ust address: (1) ownership, (2) integer seman tics, (3) data t yp es, e.g., arrays, structs, (4) shared and m utable references, including references to array fields, and (5) the p otentially un b ounded b eha vior of function calls and loops. F or space reasons, we only present an essen- tial subset of the Rust fragment supp orted b y our logic and implementation. The subset discussed in the following demonstrates how our logic and calculus mo dels Rust’s features while b eing the necessary minim um for complex problems. 3.2 Sym b olic Execution Calculus The sequent calculus contains the usual prop ositional and first-order rules, as w ell as a relatively complete rule set for mathematical integers. Since formulas ma y con tain mo dalities, we require a w ay to simplify and decomp ose the program RustyDL: A Program Logic for Rust 9 fragmen ts contained in them until only first-order formulas remain. W e follow [1,7] in the design of rule sc hemata that implemen t symb olic exe cution . Consider an if - else expression. Its execution branches in to t wo paths, dep end- ing on the v alue of its guard. Because we work with symb olic program v ariables, the calculus m ust cov er all p ossible paths and split the current pro of goal. As seen in rule ifElseSplit b elow, the rules con tain sc hematic v ariables: Γ , ∆ stand for sets of RustyDL-formulas, ϕ for one RustyDL-form ula, U for an optional up date, se for a “simple expression” (a literal or program v ariable), { b e 0 } and { b e 1 } for block expressions. A dditionally , the program modalities con tain a c ontext of the program fragmen t in focus, with inactive pr o gr am pr efix π and r emaining pr o gr am ω . Typically , π matches op ening braces and ω the remaining statements and closing braces. This context allows the rule b elow to matc h any program fragment where the expression to b e executed next is an if - else , ev en if further statements follow or it is the start of a blo ck expression. All schematic v ariables are instantiated by concrete ob jects of the corresp onding kind up on rule application. ifElseSplit Γ , U ( se . = TRUE ) = ⇒ U ⟨ π { b e 0 } ω ⟩ ϕ, ∆ Γ , U ( se . = F ALSE ) = ⇒ U ⟨ π { b e 1 } ω ⟩ ϕ, ∆ Γ = ⇒ U ⟨ π if se { b e 0 } else { b e 1 } ω ⟩ ϕ, ∆ The rule mo dels symbolic execution of a program starting with an if - else expression that has a simple (hence, side-effect free) guard. The pro of goal in the conclusion is reduced to the tw o premises corresp onding to each execution path. The sequents in the premises express that w e know the v alue of the guard se in each branch: In the then-branch w e know se to b e TRUE , in the else-branch to b e F ALSE . Matching on expressions (in contrast to statements) allows us to handle assignments o ccurring in statements ( x = y; ) and, for example, as a blo c k’s v alue ( {x = y} ), with a single rule. If the if ’s condition is a complex expression, i.e., neither a literal nor a pro- gram v ariable, w e sym b olically execute this condition b y introducing a fresh auxiliary v ariable. In the rule unfoldIfElse , nse stands for a “non-simple expres- sion” and x is a so far unused program v ariable. unfoldIfElse Γ = ⇒ U ⟨ π { let x = nse ; if x { b e 0 } else { b e 1 }} ω ⟩ ϕ, ∆ Γ = ⇒ U ⟨ π if nse { b e 0 } else { b e 1 } ω ⟩ ϕ, ∆ T o mo del state changes in the calculus, we employ updates. The rule copy b elo w matches on an y assignment of a simple expression to a program v ariable. Applying the rule adds an up date in front of the mo dality that represents the assignmen t’s state c hange and replaces the original expression of the assignment with the unit expression () . Unsurprisingly , the follo wing rule only w orks for literals or v ariables of Copy type, i.e., without mo ve seman tics. Assignmen ts causing a mo ve need a separate rule. cop y Γ = ⇒ U { x := se }⟨ π () ω ⟩ ϕ, ∆ Γ = ⇒ U ⟨ π x = se ω ⟩ ϕ, ∆ se is a literal or implements Copy 10 D. Dro dt and R. Hähnle Subsequen t applications of the cop y rule accum ulate updates in front of the mo dalit y that represent the state changes on one sp ecific path through the con- trol flow of a program fragment. The accumulated up dates are simplified and ultimately applied to the p ostcondition. In consequence, the calculus contains up date simplific ation rules. F or instance, applying an elementary up date x := t , with a program v ariable x and a term t to the program v ariable x , corresp onds to substituting x with t . In our notation: applyOnPV ( { x := t } x ) ⇝ t See App. A.1 for the complete set of up date simplification rules. T o get rid of the unit expressions left by rule copy and expressions lik e 2 in { 2; a + b } , we use rule simpleExp rStmt . This rule remov es expression statemen ts consisting of a simple expression, as these cannot affect the program state. simpleExp rStmt Γ = ⇒ U ⟨ π ω ⟩ ϕ, ∆ Γ = ⇒ U ⟨ π se ; ω ⟩ ϕ, ∆ Example 5. Let x , y , z be program v ariables of t yp e i32 . Consider the sequent x . = z = ⇒ ⟨ x = x + y; y = x - y; ⟩ ( y . = z ) . Applying copy and then simpleExprStmt t wice yields the sequent x . = z = ⇒ { x := x + y } { y := x − y } ⟨⟩ ( y . = z ) (ignoring o verflo w, to b e discussed in Section 3.4). It can b e simplified (rule not sho wn) to x . = z = ⇒ { x := x + y } { y := x − y } ( y . = z ) , after up date application and simplification it b ecomes x . = z = ⇒ ( x + y − y . = z ) , a v alid first-order sequent. 3.3 Ownership Consider an assignmen t x = y , where y is not of a t yp e implemen ting Cop y , for example, a mutable reference. Then the v alue of y is mov ed out of y to x . W e mo del this b ehavior in the logic: After the mo ve, x ’s v alue should not b e accessi- ble via y in a formula. One wa y to mo del this in logic is with explicit uninitialize d values . The do wnside is that then every v ariable on each access must b e tested whether it contains such a sp ecial v alue. This is similar to the problem s arising when using many-v alued logic to mo del undefined v alues [16]. It is more effi- cien t to rely instead on undersp e cific ation and simply use Skolemization (called anon ymizing up date in [1]). The rule move is similar to copy , but it up dates y to a fresh constant c of matching t yp e: While y still has some v alue, we know nothing ab out it b esides its sort. move Γ = ⇒ U { x := y ∥ y := c }⟨ π () ω ⟩ ϕ, ∆ Γ = ⇒ U ⟨ π x = y ω ⟩ ϕ, ∆ y : A , A is not Copy and c : A a fresh constant 3.4 Handling Integers and Overflo w Rust provides m ultiple compilation mo des with differing levels of optimization, the most prominent b eing called debug and r ele ase . The former con tains more c hecks at the cost of p erformance. These mo des also affect integer op erations. RustyDL: A Program Logic for Rust 11 An expression like a + b may o verflo w which in Rust’s semantics is un- in tended and causes a panic in debug mo de [28]. In release mo de, o v erflow c hecks are omitted and the v alue wraps around. F or v erification, we fo cus on the debug semantics, so RustyDL m ust sho w safety: the absence of o verflo ws. Rule assignAddU32 matc hes on assigning the sum of tw o simple expressions to program v ariable x with Rust type u32 . The rule splits a pro of into t wo cases. If the result of the addition is in range of the u32 type, the sum is translated to an up date. Otherwise, a panic is raised. assignAddU32 Γ , U inU32 ( se 1 + se 2 ) = ⇒ U { x := se 1 + se 2 }⟨ π () ω ⟩ ϕ, ∆ Γ , U ¬ inU32 ( se 1 + se 2 ) = ⇒ U ⟨ π panic!() ω ⟩ ϕ, ∆ Γ = ⇒ U ⟨ π x = se 1 + se 2 ω ⟩ ϕ, ∆ The semantics of the inU32 predicate is a b ounds chec k b etw een the con- stan ts u32::MIN ( = 0 ) and u32::MAX ( = 2 32 − 1 ). 3.5 Rules for References Recall that a program fragment p is only v alid if the Rust compiler—including the b orro w chec ker—accepts it. Thus, our calculus needs not to ensure that p adheres to the b orro w chec ker’s rules, instead, it can rely on its guarantees. Ho w to mo del references in RustyDL? One approach is to add a reserved program v ariable, or a ghost v ariable, call it loans , that stores which v ariables b orro w from whic h others and whether these b orrows are shared or mutable. Th us, loans represents the set of active loans at an y momen t. The reference rules ma y then b e explicitly introduced, if necessary , as axioms or rules ov er loans . Ho wev er, an explicit mo del of active loans must b e kept up-to-date after every state change . If we encounter a statement as simple as x = y , we cannot use copy , b ecause we also migh t hav e to up date loans : What if x or y are b orrow ed? Then all active loans for these v ariables must b e remov ed from loans . This approach do es not b enefit from Rust’s b orro w chec ker, but in effect re-implements it. Therefore, we do not explicitly model the activ e loans, but express references as logical terms. F or shared references, this is simple. On encountering x = &y , w e know that the v alue x is b orrowing cannot be changed while x is live. Hence, one disregards where the v alue is coming from and simply works with y ’s v alue: b o rrowSha red Γ = ⇒ U { x := sr ef ( y ) }⟨ π () ω ⟩ ϕ, ∆ Γ = ⇒ U ⟨ π x = &y ω ⟩ ϕ, ∆ The current v alue of y is held by Γ , ∆ , or U . Any further c hange to y (which m ust necessarily come after x is dropp ed or re-assigned) do es not affect x . Since x is not equiv alent to y , w e do not assign y to x but sr ef ( y ) , a shared reference term. F unction sr ef takes a term of type A and returns a term of type Ref s A . Example 6. Consider the sequent = ⇒ ⟨ y = 2; x = &y ⟩ ( x . = sr ef (2)) . Applying first rule cop y , then b orro wShared results in = ⇒ { y := 2 } { x := sr ef ( y ) } ⟨⟩ ( x . = sr ef (2)) . 12 D. Dro dt and R. Hähnle Reading v alues of shared references is done by function ∗ s A : Ref s A → A for eac h sort A (omitted in the following for readabilit y). Rule derefShared tak es an assignmen t of the dereference of y to x and translates it to an up date with ∗ s . The simplification rule derefOfSharedRef then translates the dereference of a shared reference of term t bac k to t . derefSha red Γ = ⇒ U { x := ∗ s ( y ) }⟨ π () ω ⟩ ϕ, ∆ Γ = ⇒ U ⟨ π x = *y ω ⟩ ϕ, ∆ x : A, y : Ref s A , A is Cop y derefOfSha redRef ∗ s ( sr ef ( t )) ⇝ t As shared references only consider the v alue, their treatmen t is exactly the same for shared references to arra ys. Mutable references differ in that we cannot treat them as the borrow ed v alue at time of the b orrow: If x is a mutable b orrow of y , there migh t b e an assignment suc h as *x = se , whic h changes not x ’s v alue but y ’s. Thus, x ’s v alue contains the lender and w e treat assignments to *x as assignments to this place. Consider b orrowing v ariables. When encountering a mutable borrow, for example, in x = &mut y , applying rule b o rrowMut adds an up date for x with mr ef ( T y W ) , representing that x b orrows y : b orro wMut Γ = ⇒ U { x := mr ef ( T y W ) }⟨ π () ω ⟩ ϕ, ∆ Γ = ⇒ U ⟨ π x = &mut y ω ⟩ ϕ, ∆ T o read the v alue b orrow ed b y a mutable reference, we add the unary state- dep enden t op erator ∗ m A , whic h takes a term of sort Ref m A and is itself of sort A and declare a corresp onding lo okup rule derefOfMutReference ∗ m ( mr ef ( T x W )) ⇝ x . The operator is state-dep endent to reflect an y update expressing a state c hange. This migh t b e necessary when x was mutated. T o up date a v ariable x app earing in ∗ m but k eep other state changes possibly affecting the lender, we use the rule applyElemOnDeref { u 1 ∥ x := t ∥ u 2 } ∗ m ( x ) ⇝ { u 1 ∥ u 2 } ∗ m ( t ) . A dditionally , we require a rule for mutating a v alue through the mutable reference x . W e wan t to up date not the v alue of x , but the plac e of the lender T y W . This is why we in tro duced mutating up dates t 1 ∗ → t 2 , expressing that term t 2 is written to the place b orro wed by t 1 . mutate Γ = ⇒ U { x ∗ → se }⟨ π () ω ⟩ ϕ, ∆ Γ = ⇒ U ⟨ π *x = se ω ⟩ ϕ, ∆ Example 7. Consider the sequent = ⇒ ⟨ x = 1; y = &mut x; *y = 3; ⟩ ϕ with pro- gram v ariables x : int , y : Ref m int . Applying rules copy , b orro wMut , and mutate results in = ⇒ { x := 1 } { y := mr ef ( T x W ) } { y ∗ → 3 } ⟨⟩ ϕ . T o simplify such m utating updates, w e declare t wo simplification rules. The first, applyOnMutating , applies an up date u to a mutating up date. Unlik e for elemen tary up dates (cf. T able 1 in App. A.1), u is applied to both sides of ∗ → . RustyDL: A Program Logic for Rust 13 Rule mutatingT oElementary transforms a mutating up date, where the left-hand- side is known to be mr ef ( a ) for some program v ariable a , to an elementary up date a := t . applyOnMutating { u } ( t 1 ∗ → t 2 ) ⇝ { u } t 1 ∗ → { u } t 2 mutatingT oElementa ry mr ef ( T a W ) ∗ → t ⇝ a := t Example 8. Contin uing Example 7, simplifying the first tw o up dates leads to = ⇒ { x := 1 ∥ y := mr ef ( T x W ) } { y ∗ → 3 } ⟨⟩ ϕ Because the place T x W is a c onstant , it is unaffected by up dates. F urther sim- plification yields = ⇒ { x := 1 ∥ y := mr ef ( T x W ) ∥ mr ef ( T x W ) ∗ → 3 } ⟨⟩ ϕ. Using mutatingT oElementary , w e get = ⇒ { x := 1 ∥ y := mr ef ( T x W ) ∥ x := 3 } ⟨⟩ ϕ , where w e see that y ∗ → 3 is replaced b y x := 3 . W e finally simplify a wa y the o verridden up date to = ⇒ { y := mr ef ( T x W ) ∥ x := 3 } ⟨⟩ ϕ . 3.6 Mo deling Array A ccess T o mo del array access we use tw o functions for each array t yp e [ S ; n ] : F unction get [ S ; n ] : [ S ; n ] × Field → S takes an array and a term of type Field and ev aluates it to the v alue of the arra y at the index represented by the field. F unction set [ S ; n ] : [ S ; n ] × Field × S → [ S ; n ] takes an array , a field for index i , and a v alue t and creates a new arra y term with the v alue at the index i ov erwritten with t . The sort Field allows abstraction ov er indices: F unction idx : int → Field turns an in teger in to a field. Sort Field is also used for tuple and struct fields. The present approac h follows Ja v aDL and the theory of arra ys [25]. The diffe rence is that in RustyDL the only possible aliasing is o ver the arra y index, not the array entries. Arra y access in programs is symbolically executed: If the index is out of b ounds, w e con tin ue with a panic; otherwise, w e model the successful access with an up date. Example 9. Let a : [ int ; 4] and i , j : int b e program v ariables. The term get [ int ;4] ( set [ int ;4] ( set [ int ;4] ( a , idx ( i ) , 1) , idx ( j ) , 2) , idx ( i )) represen ts an access of a at index i after first setting the v alue at index i to 1 , then writing 2 at index j . Assuming i and j are v alid indices, the v alue of this term is 2 iff i . = j , and 1 otherwise. W e pretty-prin t a term like get [ S ; n ] ( t 0 , idx ( t 1 )) as t 0 [ t 1 ] and omit the type of set . The term in Example 9 can b e typeset as set ( set ( a , idx ( i ) , 1) , idx ( j ) , 2)[ i ] . F or reading and writing to array indices, w e use the functions ab ov e. Rule cop yArrayIdx below models reading an array a at index i and storing the result in x . W e split the proof to c heck for p ossible out of b ound errors which cause a panic. W e omit the similar mov e version. 14 D. Dro dt and R. Hähnle Listing 2. A program computing the pro duct of a and b , storing it in n . Because we use b reak with a v alue, the result is also the lo op’s v alue. let mut n = 0; let old_b = b; lo op { if b == 0 { b reak n; } n += a; b − = 1; } cop yArrayIdx Γ , U ( i < 0 ∨ i ≥ n ) = ⇒ U ⟨ π panic!() ω ⟩ ϕ, ∆ Γ , U (0 ≤ i ∧ i < n ) = ⇒ U { x := a [ i ] }⟨ π () ω ⟩ ϕ, ∆ Γ = ⇒ U ⟨ π x = a[i] ω ⟩ ϕ, ∆ x : S, a : [ S ; n ] , S is Cop y Similarly , the rule writeArrayIdxCop y handles writing v alue x to an arra y a at index i . W e add an up date changing the v alue of a . writeArra yIdxCopy Γ , U ( i < 0 ∨ i ≥ n ) = ⇒ U ⟨ π panic!() ω ⟩ ϕ, ∆ Γ , U (0 ≤ i ∧ i < n ) = ⇒ U { a := set ( a , idx ( i ) , se ) }⟨ π () ω ⟩ ϕ, ∆ Γ = ⇒ U ⟨ π a[i] = se ω ⟩ ϕ, ∆ a : [ S ; n ] , S is Copy References of arra y fields follow the same general approach as in Section 3.5, but are more complex, b ecause inv alid array b ounds ma y cause a panic which m ust b e chec ked. Details are in App. A.2. 3.7 Lo op Inv arian ts and Lo op Scop es F unction calls and lo ops introduce p otentially un b ounded computation and, therefore, require some form of induction principle in deductiv e program v er- ification to achiev e relative completeness. Our approach is designed to handle function calls as well as lo ops in their full complexity , where treatment of the former closely follo ws [1]. F or space reasons, we discuss it only in App. B. Instead, we discuss lo ops in some detail: While lo op in v arian ts are a standard concept, the rules b ecome highly complex for loops that may terminate due to b reak or panic . W e use the relatively recent concept of lo op sc op e [31] to pro vide concise inv ariant rules that handle non-standard con trol flow. (F or simplicity , we disregard lab els, ho wev er, the presented approach is designed to handle them.) Consider the program in Listing 2, where a , b , old_b are program v ariables of t yp e u64 . The lo op is an expression and has a v alue: the v alue of n in the final iteration. T o safely approximate the unkno wn num b er of lo op iterations, we use a lo op inv arian t: a RustyDL formula inv that holds b efore the first and after ev ery subsequen t iteration. Ignoring ov erflow, a sufficien tly strong loop in v ariant inv to pro ve functional correctness of the lo op in Listing 2 is the formula (we need not strengthen the in v ariant with 0 ≤ b , as this is inferred from b ’s type): n . = a · ( old_b − b ) ∧ b ≤ old_b (1) RustyDL: A Program Logic for Rust 15 The Rust compiler translates all loops to unconditional lo ops as in Listing 2, so the inv arian t rules of Hoare [18], Dijkstra [13], or Jav aDL [1], are unsuitable: All make a case distinction based on the guard’s v alue. This is imp ossible here, as w e are unable to extract a lo op guard. T o address this and to supp ort statements redirecting control flo w, such as break and return , we use an auxiliary control statemen t: A lo op sc op e ⟳ x p ⟲ with index variable x and bo dy p is a Rust expression, where x is a fresh program v ariable of type b o ol . In tuitively , a lo op scop e represen ts a single generic loop iteration and records the reason for exiting the lo op in the index v ariable x . F or example, wh en exiting the lo op through a break , x is set to true . When the lo op is not exited this wa y , w e set x to false . Like loops themselves, loop scop es in RustyDL are expressions and can hav e a v alue, namely the v alue of the lo op they represen t. W e use lo op scop es in the loop inv ariant rule to symbolically execute a generic lo op iteration and to record how it was terminated. Rule lo opScop eInvBo x b elow matc hes on lo ops in the b o x mo dality . It splits a pro of into tw o branches. lo opScopeInvBox Γ = ⇒ U inv , ∆ Γ = ⇒ U V  inv → [ ⟳ x b o dy ; continue; ⟲ ]  ( x . = TRUE → [ π ω ] ϕ ) ∧ ( x . = F ALSE → inv )   , ∆ Γ = ⇒ U [ π lo op b o dy ω ] ϕ, ∆ The first branch ensures that inv holds when en tering the lo op. The second branc h puts the lo op bo dy in to a lo op scop e with fresh index v ariable x and sym b olically executes the lo op b o dy once, assuming the in v ariant holds. Then, the v alue of the index v ariable x is read to determine whether the lo op e xits ( x . = TRUE ) or further iterations are executed ( x . = F ALSE ). In the first case, w e must show that p ostcondition ϕ holds, in the second that inv remains v alid. The rule executes a generic lo op iteration, where the v alue of program v ari- ables o ccurring in Γ ∪ ∆ is not known. Therefore, the v alue of such v ariables in Γ ∪ ∆ m ust b e “forgotten.” This is achiev ed b y a so-called anonymizing up date V [1] that assigns a fresh, unknown v alue to any v ariable written to in b o dy . The v alue of index v ariable x is set during symbolic execution of the lo op scop e by the rules handling break , continue , return , etc. W e illustrate the mec h- anism with rules b reakV alue and continue and a trailing program p ; see also [31]. Observ e how breakV alue returns the lo op scop e’s v alue se . b reakValue Γ = ⇒ U ⟨ π { x = true; se } ω ⟩ ϕ, ∆ Γ = ⇒ U ⟨ π ⟳ x b reak se ; p ⟲ ω ⟩ ϕ, ∆ continue Γ = ⇒ U ⟨ π x = false ω ⟩ ϕ, ∆ Γ = ⇒ U ⟨ π ⟳ x continue; p ⟲ ω ⟩ ϕ, ∆ Example 10. Consider the program in Listing 2. Let ϕ ≡ n . = a · old_b and inv as in (1). After sym b olically executing the assignments and simplifying, w e hav e = ⇒ { n := 0 ∥ old_b := b } [ loop { ... } ] ϕ. Applying lo opScop eInvBo x yields tw o sequents: first, the “initially v alid” case: = ⇒ { n := 0 ∥ old_b := b } ( n . = a · ( old_b − b ) ∧ b ≤ old_b ) 16 D. Dro dt and R. Hähnle whic h is simplified to = ⇒ 0 . = a · ( b − b ) ∧ b ≤ b . Integer simplification prov es this branc h. The second branch of lo opScop eInvBox yields: = ⇒{ n := 0 ∥ old_b := b } { n := c n ∥ b := c b }  inv → [ ⟳ x if b == 0 ... ⟲ ] (2)  ( x . = TRUE → [ ] ϕ ) ∧ ( x . = F ALSE → inv )  | {z } ψ  Simplification and symbolically executing the if in (2) yields tw o branches (3), (4), where u ≡ old_b := b ∥ n := c n ∥ b := c b and inv ′ ≡ { u } inv : inv ′ , c b . = 0 = ⇒ { u } [ ⟳ x b reak n; n+=a; b-=1; continue; ⟲ ] ψ (3) inv ′ , c b  . = 0 = ⇒ { u } [ ⟳ x n+=a; b-=1; continue; ⟲ ] ψ (4) Applying b reakValue to (3) drops the remaining lo op b o dy and the lo op scop e: inv ′ , c b . = 0 = ⇒ { u } [ { x = true; n } ] ψ (5) Hence, w e know x is true in this branch, allowing us to simplify the sequent to inv ′ , c b . = 0 = ⇒ { u } ( n . = a · old_b ) (6) Applying the up dates yields (7), easily pro ven by arithmetic simplification. c n . = a · ( b − c b ) ∧ c b ≤ b , c b . = 0 = ⇒ c n . = a · b (7) The pro of steps for (4) follo w similar lines. 3.8 Implemen tation of Calculus Rules W e implemented RustyDL and its calculus based on the Ke Y to ol, 1 lev eraging the general data structures and rule matching algorithm of that system. The pro of system is not foundational; we rely on theories of integers, sequences, etc. All rules are implemented in a domain sp ecific language for writing schematic program logic rules in a sequent calculus called “taclets” [7]. They can b e re- view ed, c heck ed, and partially , ev en mechanically prov en independently from the remaining system. There has also b een w ork to prov e the rules sound rela- tiv e to program semantics and to v alidate the transformation rules [3,33]. The Ke Y system is time-pro ven. Thus, w e argue, the core is trust worth y and the implementation faithfully reflects the calculus rules abov e. A t the moment, the implemen tation is a prototype able to manually and automatically prov e a n umber of examples sho wcasing several Rust features, in particular, b orrowing, lo ops, arrays, tuples, and (some) en ums. Most instructiv e is a v erification of binary searc h, creating a pro of of 4260 rule applications in 2 . 1 seconds. 1 See implemen tation and examples at https://gith ub.com/Dro dt/k ey/tree/rusty. RustyDL: A Program Logic for Rust 17 4 Related W ork Our approach to deductive v erification w as inspired by Ke Y [1]. Sev eral other to ols hav e b een dev elop ed in the last years to verify Rust programs. Cr eusot [12] translates safe Rust programs to the in termediate language and verification to ol Why3 [15] whic h generates v erification conditions that are discharged with SMT solv ers. Mutable references are mo deled using prophecies [20], in effect writ- ing the v alue of the mutable reference bac k to the lender once the reference is dropp ed. The Prusti to ol [4] is similar to Creusot, but based on separation logic, and translates to Vip er [26]. A ene as [17] translates safe Rust to several functional bac k ends, remo ving mutable references entirely . All these to ols rely on translation to in termediate languages and allow no in teraction, whereas our dynamic logic p ermits a HIL in teraction pattern. V erus [22] enables verification of unsafe Rust, based on v erification condition generation and SMT solving. Presently , it is unclear how RustyDL migh t b e extended to unsafe Rust, the “h ybrid” approach of [6] b eing strong contender. An alternative to logic mo deling of references are p ermissions [10] to deal with read and read-and-write p ermissions to memory lo cations. Permissions w ere implemen ted in the Ke Y tool in [19], but here we chose m utable up dates, b ecause they cause less o verhead and easily extend the existing up date mechanism. 5 Conclusion and F uture W ork W e presented RustyDL, a dynamic logic for a subset of Rust, as well as a se- quen t calculu s to pro ve v alidity of RustyDL formulas. W e gav e solutions for logic mo deling of crucial Rust features with reasonable complexit y . This demonstrates that the Ke Y approach to HIL program verification can b e applied to Rust. The RustyDL calculus presented here was implemented on top of the Ke Y v erification to ol as a pro of-of-concept. W e used that implementation to success- fully verify several Rust functions, including ones containing lo ops and refer- ences, ho wev er, a fuller ev aluation needs to wait until the degree of automation is sufficien tly high. A ccordingly , future w ork can be divided in to tw o directions: First, extending the subset of Rust to include T raits, iterators, pattern matc hing, and generic functions. This is time consuming, y et straigh tforw ard. V erification of unsafe Rust also should b e considered, but one has to carefully weigh to which extent. It is likely that unsafe Rust requires a more explicit memory mo del than we in- tro duced here; still, one w ould like this mo del to b e minimal and refer to the safe v ersion where p ossible. A thorough inv estigation remains to be done. Second, we are impro ving the implementation: flesh out the specification language, similar to JML [23] and implemen t a graphical user in terface. 18 D. Dro dt and R. Hähnle References 1. Ahrendt, W., Beck ert, B., Bub el, R., Hähnle, R., Schmitt, P .H., Ulbrich, M. (eds.): Deductiv e Softw are V erification – the KeY Book: F rom Theory to Practice. No. 10001 in LNCS, Springer (2016). https://doi.org/10.1007/978- 3- 319- 49812- 6 2. Ahrendt, W., Beck ert, B., Bubel, R., Hähnle, R., Ulbric h, M.: The many uses of dynamic logic. In: Ern st, G., Güdemann, M., Knapp, A., Nafz, F., Ortmeier, F., Ponsar, H., Schellhorn, G., Sc hiendorfer, A. (eds.) Go Where the Bugs Are – Essays Dedicated to W olfgang Reif on the Occasion of His 65th Birthday . LNCS, v ol. 15765, pp. 56–82. Springer, Cham (Jun 2025). h ttps://doi.org/10.1007/ 978- 3- 031- 92196- 4_4 3. Ahrendt, W., Roth, A., Sasse, R.: Automatic v alidation of transformation rules for Jav a verification against a rewriting semantics. In: Sutcliffe, G., V oronko v, A. (eds.) Logic for Programming, Artificial In telligence, and Reasoning. pp. 412–426. Springer, Berlin, Heidelb erg (2005). h ttps://doi.org/10.1007/11591191_29 4. Astrausk as, V., Bílý, A., Fiala, J., Grannan, Z., Matheja, C., Müller, P ., P oli, F., Summers, A.J.: The Prusti pro ject: F ormal v erification for Rust. In: Desh- m u kh, J.V., Hav elund, K., P erez, I. (eds.) NASA F ormal Methods – 14th In terna- tional Symp osium, NFM 2022, Pasadena, CA, USA, May 24–27, 2022, Proceed- ings. pp. 88–108. No. 13260 in LNCS, Springer (2022). https://doi.org/10.1007/ 978- 3- 031- 06773- 0_5 5. Astrausk as, V., Matheja, C., Poli, F., Müller, P ., Summers, A.J.: Ho w do program- mers use unsafe Rust? Proc. ACM Program. Lang. 4 (OOPSLA), 136:1–136:27 (2020). h ttps://doi.org/10.1145/3428204 6. A y oun, S.É., Denis, X., Maksimović, P ., Gardner, P .: A hybrid approac h to semi- automated Rust v erification. arXiv preprint arXiv:2403.15122 (2024) 7. Bec kert, B., Bubel, R., Drodt, D., Hähnle, R., Lanzinger, F., Pfeifer, W., Ulbric h, M., W eigl, A.: The Jav a verification tool KeY: A tutorial. In: Platzer, A., Rozier, K.Y., Pradella, M., Rossi, M. (eds.) F ormal Methods. pp. 597–623. No. 14934 in LNCS, Springer Nature Switzerland, Cham (2025). h ttps://doi.org/10.1007/ 978- 3- 031- 71177- 0_32 8. Bec kert, B., Sanders, P ., Ulbrich, M., Wiesler, J., Witt, S.: F ormally verifying an efficien t sorter. In: Finkb einer, B., K ovács, L. (eds.) T ools and Algorithms for the Construction and Analysis of Systems – 30th In ternational Conference, T ACAS 2024, Held as P art of the Europ ean Joint Conferences on Theory and Practice of Soft ware, ET APS 2024, Luxem b ourg City , Luxembourg, April 6–11, 2024, Pro- ceedings, Part I. pp. 268–287. No. 14570 in Lecture Notes in Computer Science, Springer (2024). ht tps://doi.org/10.1007/978- 3- 031- 57246- 3_15 9. Blanc, A.L., Lam, P .: Surveying the Rust v erification landscap e. CoRR abs/2410.01981 (2024). https://doi.org/10.48 550/ARXIV.2410.01981 10. Bo yland, J.: F ractional permissions. In: Clarke, D., Noble, J., W rigstad, T. (eds.) Aliasing in Ob ject-Orien ted Programming. Types, Analysis and V erifica- tion, LNCS, vol. 7850, pp. 270–288. Springer (2013). https://doi.org/10.1007/ 978- 3- 642- 36946- 9_10 11. De Gouw, S., Rot, J., De Boer, F.S., Bub el, R., Hähnle, R.: Op enJDK’s ja v a.utils.Collection.sort() is broken: The go o d, the bad and the w orst case. In: Kro ening, D., Pasarean u, C. (eds.) Pro c. 27th Intl. Conf. on Computer Aided V er- ification (CA V), San F rancisco. LNCS, vol. 9206, pp. 273–289. Springer (Jul 2015) 12. Denis, X., Jourdan, J.H., Marché, C.: Creusot: A foundry for the deductiv e verifi- cation of Rust programs. In: Riesco, A., Zhang, M. (eds.) ICFEM 2022, Pro ceed- RustyDL: A Program Logic for Rust 19 ings. pp. 90–105. No. 13478 in LNCS, Springer (2022). https://doi.org/10.1007/ 978- 3- 031- 17244- 1_6 13. Dijkstra, E.W., Sc holten, C.S.: Predicate Calculus and Program Semantics. Mono- graphs in Computer Science, Springer, New Y ork, NY, 1 edn. (1990). https: //doi.org/10.1007/978- 1- 4612- 3228- 5 14. Din, C.C., Hähnle, R., Henrio, L., Johnsen, E.B., Pun, V.K.I., T apia T arifa, S.L.: Lo cally abstract, globally concrete seman tics of concurrent programming lan- guages. T ransactions on Programming Languages and Systems 46 (1), 3:1–3:58 (Mar 2024). h ttps://doi.org/10.1145/3648439 15. Filliâtre, J.C., P ask evic h, A.: Why3 — where programs meet prov ers. In: F elleisen, M., Gardner, P . (eds.) Pro ceedings of the 22nd Europ ean Symposium on Program- ming. Lecture Notes in Computer Science, v ol. 7792, pp. 125–128. Springer (Mar 2013) 16. Hähnle, R.: Man y-v alued logic, partiality , and abstraction in formal sp ecification languages. Logic Journal of the IGPL 13 (4), 415–433 (2005) 17. Ho, S., Protzenko, J.: Aeneas: Rust v erification b y functional translation. Pro c. A CM Program. Lang. 6 (ICFP), 711–741 (2022). h ttps://doi.org/10.1145/3547647 18. Hoare, C.A.R.: An axiomatic basis for computer programming. Communications of the A CM 12 (10), 576–580 (Oct 1969). https://doi.org/10.1145/363235.363259 19. Huisman, M., Mostowski, W.: A sym b olic approach to p ermission accounting for concurren t reasoning. In: Grosu, D., Jin, H., Papadopoulos, G. (eds.) 14th In terna- tional Symp osium on Parallel and Distributed Computing, ISPDC 2015, Limassol, Cyprus, June 29 - July 2, 2015. pp. 165–174. IEEE Computer Society (2015). h ttps://doi.org/10.1109/ISPDC.2015.26 20. Jung, R., Lepigre, R., Parthasarath y , G., Rap op ort, M., Timany , A., Dreyer, D., Jacobs, B.: The future is ours: Prophecy v ariables in separation logic. Proc. ACM Program. Lang. 4 (POPL), 45:1–45:32 (2020). https://doi.org/10.1145/3371113 21. Klabnik, S., Nic hols, C.: The Rust Programming Language. No Starch Press, San F rancisco, CA, 2 edn. (Dec 2022), https://doc.rust- lang.org/stable/bo ok/ 22. Lattuada, A., Hance, T., Cho, C., Brun, M., Subasinghe, I., Zhou, Y., How ell, J., P arno, B., Ha wblitzel, C.: V erus: V erifying Rust programs using linear ghost types. Pro c. A CM Program. Lang. 7 (OOPSLA1), 286–315 (2023) 23. Lea vens, G.T., Poll, E., Clifton, C., Cheon, Y., Rub y , C., Cok, D., Müller, P ., Kiniry , J., Chalin, P ., Zimmerman, D.M., Dietl, W.: JML Reference Manual (Ma y 2013), h ttp://www.eecs.ucf.edu/~lea vens/JML//OldReleases/jmlrefman.pdf 24. Li, H., Guo, L., Y ang, Y., W ang, S., Xu, M.: An empirical study of Rust-for-Linux: The success, dissatisfaction, and compromise. In: Bagchi, S., Zhang, Y. (eds.) Pro- ceedings of the 2024 USENIX Annual T ec hnical Conference, USENIX A TC 2024, San ta Clara, CA, USA, July 10–12, 2024. pp. 425–443. USENIX Asso ciation (2024), h ttps://www.usenix.org/conference/atc24/presen tation/li- hongyu 25. McCarth y , J.: T ow ards a mathematical science of computation. In: Information Pro cessing, Pro ceedings of the 2nd IFIP Congress 1962, Munich, Germany , August 27 - Septem b er 1, 1962. pp. 21–28. North-Holland (1962) 26. Müller, P ., Sc hw erhoff, M., Summers, A.J.: Viper: A verification infrastructure for p ermission-based reasoning. In: Jobstmann, B., Leino, K.R.M. (eds.) V erification, Mo del Checking, and Abstract In terpretation. pp. 41–62. Springer Berlin Heidel- b erg, Berlin, Heidelberg (2016) 27. Pratt, V.R.: Semantical considerations on Floyd-Hoare logic. In: 17th Annual Sym- p osium on F oundations of Computer Science, Houston, TX, USA. pp. 109–121. IEEE, Los Alamitos, CA (1976). https://doi.org/10.1109/SF CS.1976.27 20 D. Dro dt and R. Hähnle 28. Rust Communit y: The Rust reference. Reference, Rust F ound ation (2024), https: //do c.rust- lang.org/stable/reference/introduction.html 29. Rust Comm u nit y: Rust Compiler Developmen t Guide (2025), https:// rustc- dev- guide.rust- lang.org/ 30. Sc hellhorn, G., Bo denmüller, S., Bitterlich, M., Reif, W.: Softw are & system ver- ification with KIV. In: Ahrendt, W., Beck ert, B., Bub el, R., Johnsen, E.B. (eds.) The Logic of Softw are. A T asting Menu of F ormal Metho ds – Essays Dedicated to Reiner Hähnle on the Occasion of His 60th Birthday . LNCS, vol. 13360, pp. 408–436. Springer (2022). https://doi.org/10.1007/ 978- 3- 031- 08166- 8_20 31. Steinhöfel, D., W asser, N.: A new in v ariant rule for the analysis of loops with non-standard control flows. In: P olik arp ov a, N., Sc hneider, S.A. (eds.) Integrated F ormal Methods - 13th In ternational Conference, IFM 2017, T urin, Italy , Septem- b er 20-22, 2017, Proceedings. pp. 279–294. No. 10510 in LNCS, Springer (2017). h ttps://doi.org/10.1007/978- 3- 319- 66845- 1_18 32. Thompson, C.: Ho w Rust w en t from a side pro ject to the w orld’s most-lo ved pro- gramming language. MIT T echnology Review 126 (2) (F eb 2023), https://www. tec hn ologyreview.com/2023/02/14/1067869/ 33. T ren telman, K.: Proving correctness of Jav aCard DL taclets using Bali. In: Pro- ceedings of the Third IEEE International Conference on Softw are Engineering and F ormal Methods. pp. 160–169. SEFM ’05, IEEE Computer So ciety , USA (Sep 2005). h ttps://doi.org/10.1109/SEFM.2005.37 A Calculus Rules A.1 Up date Simplification Rules See T able 1. A.2 Rules for Arra y References Consider rule b orro wMutIdxArr : W e w ant to get a mutable reference to index i of the array referenced by a . Again, we p erform a b ounds chec k. W e up date x to a m utable reference of place a [ i ] . b o rrowMutIdxArr Γ , U ( i < 0 ∨ i ≥ n ) = ⇒ U ⟨ π panic!() ω ⟩ ϕ, ∆ Γ , U (0 ≤ i < n ) = ⇒ U { x := mr ef ( T a , i W ) } ⟨ π () ω ⟩ ϕ, ∆ Γ = ⇒ U ⟨ π x = &mut a[i] ω ⟩ ϕ, ∆ a : Ref m [ A ; n ] , x : Ref m A , i : int Example 11. Let a : [ int ; 4] , x : Ref m [ int ;4] , y : Ref m int b e program v ariables. W e start in the sequen t = ⇒ ⟨ x = &mut a; y = &mut x[0]; *y = 8; ⟩ ϕ . F ull symbolic execution of the program fragment yields the sequent = ⇒ { x := mr ef ( T a W ) | {z } r 1 } { y := mr ef ( T x , 0 W ) } { y ∗ → 8 } ϕ . RustyDL: A Program Logic for Rust 21 { u 1 ∥ a := t 1 ∥ u 2 ∥ a := t 2 ∥ u 3 } t ⇝ { u 1 ∥ u 2 ∥ a := t 2 ∥ u 3 } t where t ∈ T rm A ∪ Fml ∪ Up d { u 1 ∥ a := t ′ ∥ u 2 } t ⇝ { u 1 ∥ u 2 } t where t ∈ T rm A ∪ Fml ∪ Up d , a ∈ fpv ( t ) { u 1 } { u 2 } t ⇝ { u 1 ∥ { u 1 } u 2 } t where t ∈ T rm A ∪ Fml ∪ Up d { u ∥ skip } t ⇝ { u } t where t ∈ T rm A ∪ Fml ∪ Up d { skip ∥ u } t ⇝ { u } t where t ∈ T rm A ∪ Fml ∪ Up d { skip } t ⇝ t where t ∈ T rm A ∪ Fml ∪ Up d { u } v ⇝ v where v ∈ VSym { u } f ( t 1 , . . . , t n ) ⇝ f ( { u } t 1 , . . . , { u } t n ) where f ∈ FSym ∪ PSym { u } (if ϕ then t 1 else t 2 ) ⇝ if { u } ϕ then { u } t 1 else { u } t 2 { u } ¬ ϕ ⇝ ¬{ u } ϕ { u } ( ϕ • ψ ) ⇝ { u } ϕ • { u } ψ where • ∈ {∧ , ∨ , → , ↔} { u } Q A v ; ϕ ⇝ Q A v ; { u } ϕ where Q ∈ {∀ , ∃} , v ∈ fv ( u ) { u } ( a := t ) ⇝ a := { u } t { u } ( t 1 ∗ → t 2 ) ⇝ { u } t 1 ∗ → { u } t 2 { u } ( u 1 ∥ u 2 ) ⇝ { u } u 1 ∥ { u } u 2 mr ef ( T a W ) ∗ → t ⇝ a := t mr ef ( T mref ( T a W ) , t 1 W ) ∗ → t 2 ⇝ a := set ( a , t 1 , t 2 ) { a := t } a ⇝ t T able 1. Simplification rules for up dates Simplifying the first tw o up dates gives us the following sequent. Observ e that w e use the mutable reference stored in x within y ’s v alue. = ⇒ { x := r 1 ∥ y := mr ef ( T r 1 , 0 W ) | {z } u 1 } { y ∗ → 8 } ϕ Com bining the up dates yields the following sequent: = ⇒ { u 1 ∥ mr ef ( T r 1 , 0 W ) ∗ → 8 } ϕ Next, w e resolve the mutating up date using mutatingArrT oElementary : = ⇒ { u 1 ∥ a := set ( a , idx (0) , 8) } ϕ . B Applying F unction Con tracts Consider a function call r = f(p 1 , . . . , p n ) , where f is a function with arguments a 1 : T 1 , . . . , a n : T n and p 1 , . . . , p n , r are program v ariables of compatible types. There are t wo wa ys of resolving such a call in RustyDL. First, w e can “inline” the bo dy of function f , creating local v ariables for parameters, syn tactically replacing return expressions with assignments to r , etc. This approac h cannot handle unbounded recursion, leads to repeated symbolic execution of the same Rust co de and, therefore, often results in state explosion. 22 D. Dro dt and R. Hähnle useFnContract Γ = ⇒ U V ( pr e ∧ p aramsInR ange ∧ trm ≺ mby ) , ∆ Γ = ⇒ U V W ( p ost ∧ inR ange T ( res ) → ⟨ π r = res ω ⟩ ϕ ) , ∆ Γ = ⇒ U ⟨ π r = f( se 1 , . . . , se n ) ω ⟩ ϕ, ∆ Where – fn f(a 1 : T 1 , . . . , a n : T n ) − > T is a function, – ( pr e , p ost , trm ) is a contract for f , – V := { a 1 := se 1 ∥ · · · ∥ a n := se n } ∈ Up d initializes the parameters, – W := { b 1 ∗ → c 1 ∥ · · · ∥ b m ∗ → c m } is an anonymizing up date, – b 1 : Ref m A 1 , . . . , b m : Ref m A m are exactly the parameters of f with m utable reference t yp e, – c 1 , . . . , c m are fresh constan ts, – and mb y is a termination witness if this is a recursive call. Fig. 2. F unction contract rule Second, we can use a suitable declarative abstraction to reason ab out f ’s b e- ha vior without symbolically executing its b o dy . T o accomplish this, Definition 6 in tro duces the notion of a function’s c ontr act . Definition 6 (F unction Con tract). L et Crt b e a valid R ust cr ate and let fn f(a 1 : T 1 , . . . , a n : T n ) − > T b e a function in Crt . A triple ( pr e , p ost , trm ) is a con tract for f , c onsisting of: (1) formula pr e as pr e c ondition, (2) formula p ost as p ostc ondition, (3) an optional term trm for the termination witness. These c omp onents have ac c ess to the ar guments of f , a 1 : T 1 , . . . , a n : T n , and the p ostc ondition in addition has ac c ess to the r esult of f , res : T . The rule useFnContract in Fig. 2 replaces a function call with the function’s con tract, assuming that this contract is correct, which must b e pro ven separately . The rule has t wo premises: The first establishes that the function’s precon- dition is satisfied in the curren t symbolic state, after V up dated the formal parameters to the actual parameters. A dditionally , we show that the termina- tion witness is strictly decreasing, with ≺ a well-founded binary relation o ver trm ’s type. This is necessary for recursive calls: If trm strictly decreases with eac h recursive call, well-foundedness ensures that recursion is b ounded and the call terminates. The second premise allo ws us to assume the function’s postcondition and con tinue with the pro of. How ever, the called function may c hange parts of the state if we pass a mutable reference. Therefore, we add the anonymizing up date W , which assigns fresh constants to all p ossibly mutated places, see Section 3.7 for further explanations. Example 12. Consider the recursiv e function fn mul(a: u64 , b: u64 , c: & mut u64 ) with contract (true , ∗ m ( c ) . = a · b , — ) . It computes the result of multiplying a times b and writes the result to c . RustyDL: A Program Logic for Rust 23 Assume the goal sequen t has the following shap e with program v ariables x , y of t yp e u64 , z of type & mut u64 , and r of type () : inRange u64 ( x ) , inRange u64 ( y ) , inRange &mut u64 ( z ) = ⇒ ⟨ r = mul(x, y , z); ⟩ ϕ Applying fnUseContract yields as first premise the sequen t: inRange u64 ( x ) , inRange u64 ( y ) , inRange &mut u64 ( z ) = ⇒{ a := x ∥ b := y ∥ c := z | {z } V } (true ∧ p ar amsInR ange ) This can easily b e disc harged, b ecause all parameters are in range of their resp ectiv e types. The second sequent lo oks, slightly simplified, as follows: inRange u64 ( x ) , inRange u64 ( y ) , inRange &mut u64 ( z ) = ⇒V { c ∗ → d } ( ∗ m ( c ) . = a · b → ⟨ r = res; ⟩ ϕ ) where d is a fresh constan t of sort int , anonymizing c ’s lender. The fact that c con tains the pro duct of a and b can now b e used in the subsequen t pro of of ϕ .

Original Paper

Loading high-quality paper...

Comments & Academic Discussion

Loading comments...

Leave a Comment