Rustan Leino: Good afternoon, everyone. Thank you all for coming. I'm Rustan Leino. And today I have the pleasure to introduce Bart Jacobs, who has been an intern with us for a long stretch of time, about a year almost, I think. And he was extremely productive during those internships and we were sad to see him go, but it was good that he did, because he has gone and done wonderful things in all those years. And one of those achievements has been -- well, he's worked on checkers for dynamic frames, for implicit dynamic frames. I think implicit dynamic frames, some of those ideas he had when he was here and he was trying to convince me that he they were good ideas, and in retrospect they were really great ideas. So he's in the last several years been working quite a bit with his VeriFast verifier, which takes both C and Java as input. And then the programs are specified using separation logic. Now separation logic is used in many contexts. But his tool really makes it a reality that you can really make use of it in specifying the programs. And if you see Bart only every so often, you turn around and then all of a sudden there are new wonderful features that are so impressive in the tool, and that happens constantly. So today he's going to tell us about one of those, which is modular, I think, termination tracking. He'll probably explain what it is. Bart Jacobs: Thanks, Rustan. Actually I'll be talking about four new features. Actually, three new features and one new one, one proof that all of it actually -Rustan Leino: Done in the last month. Bart Jacobs: I know. So let's see if we get through all of it. So actually these are the things that I will talk about. The first one is modular termination verification. So recently I added support for proving not just partial correctness but also total correctness of programs with VeriFast. So you can prove that programs terminate. Or if you don't want them to terminate, you can also prove that they are live, that they are responsive. So that's the first thing I will talk about. The second thing is the part which is not really a new feature, but which is like the foundations of some of VeriFast. So this is in the context of recent developments in the concurrency verification world. So people have come up with very intricate and very sophisticated higher-order logic for very specifying or verifying concurrency. But actually there is a very unsophisticated way of verifying fine-grained concurrency, and that's what is implemented in VeriFast. By the way all of these sophisticated ones don't have to support yet. They only exist in proof assistance, so you have to use them very laboriously and interactively. This one is supported in VeriFast. I will talk about that. And thirdly, I will talk about provably live exception handling. So most programs that you write, at least in Java, they will not be live. That is in the presence of exceptions they might deadlock. And in .NET it's slightly better because Insurgent 2.0, if that terminates within an exception, the entire process is killed. But that's still not quite the behavior you want. So I'll talk about how to deal with that and how to verify then these programs in the third part. And the fourth part I will talk about verifying Hello World. So we recently achieved verification of Hello World after a long time. So usually people, I mean it's the sites. People don't really concentrate in that kind of programs. Typically we only prove that they don't crash. We don't prove that the program does something. And so then in the fourth part I will talk about features for specifying and verifying that the program actually does something useful. So let's first look at modular termination verification. For that I have a different slide tech. All right. So this is work I did with people in the Netherlands. So first what is the problem precisely? So it's not just termination verification, it's modular termination verification. That's what makes it slightly new, the approach. I mean people have known how to verify termination for a long time. It's harder to see how to do it in a modular rate, especially in the presence of dynamic binding. So if you have a programming language like, for example, Java or C# with interfaces, and the methods are virtual methods, therefore are dynamically bound, how to give a contract, how to specify these methods such that you can prove such that the proof of each module separately, when taken together, implies that the entire program terminates. That's the question. So for example, how to specify these methods. And you want to prevent, for example, that this implementation verifies. You want to reject this implementation. This is one where the intersection methods delegates the work to the object that's passed in as an argument. So you want to check whether the received object intersects some other set, and to do that you ask the other set whether it intersects you. For partial correctness, that's perfectly fine. For total correctness, that's not fine, because you're not doing any work. So if you instantiate such a set and then you call intersect on it passing itself in, then you have an infinite loop, so how to catch that. So what, of course one proof system is one that allows everything. That cannot prove everything, that's perfectly sound, that's not useful. So we want to allow many programs, including this one, so we have an implementation of sets, that is the empty sets, so it doesn't contain anything, it doesn't intersect with anything. We have this sets that equals other sets, plus one element inserted into it, so it contains some elements X if X equals elem, or the other set contains X. And then this set intersects with other set if the other set contains, actually this should be elem instead of this. Or the other set intersects with other. Okay, so that's fine. We want to also support if a set is based on top of another set. For example, if you defined a union of two sets, for example this way, and then you identify another union. So the reason why I give this example is because you might think that you can prove termination by looking at the size of the sets that has to go down at each call. But here, the size the sets doesn't go down at each call, because here you simply delegate to another class without doing any work yourself. But it's the class that's earlier in the program. And we also want to support that. So these are some examples that you want to support. Now, the proposed solution is built on top of separation logic. So at first I'm going to quickly recall separation logic for object oriented programs before adding the new feature that I propose. So how to specify partial correctness for such an interface. So what you typically define in this separation logic is you define a predicate that abstract needed notes, the resources required by the set. So more specifically it will denote the memory locations that comprise the set's representation. So the fields of the objects that the set uses to store its own state. That's what is represented by this predicate. So then the contents method will require that you give it access to the set since the contents method doesn't modify the set, it only requires some fractional permission to it. And we don't care how much the fraction is, so it's an underscore. This means give me some fraction of the permission to access the set so we can read the set modified, and we will give back some fraction of permission. The same for intersects. We require access to the sets, so this set as well as to the other set and we give it back. So now, what is the theory underlying that? It's as follows. Or actually I think it's just that this title is in the wrong place. We're already in the middle of explaining this. So what is an implementation of this interface? What does that look like? Well, for example, for insert, we give a body to this predicate as follows. The representation of an insert object consists of the elem field. So we have to have permission to access the elem field. We don't care about its value, and separately we have to have permission to access the sets zero field. So this, by the way, this syntax for declaring a task, is like scalar, so you declared the fields of the class in parentheses after the class name. So this is scalar syntax for declaring fields of the class. So we have access to the elem field, to the set zero field, as well as to the set object pointed to by set zero. So you can have recursion here. So set is defined in terms of set itself, for another object in this case. But since it's an inductive predicate, so long as the recursive occurrence occurs in positive positions, it always has a well-defined meaning. So this is how we define the representation of a set. And then the body of the methods can be proved easily against the specification. And the client [indiscernible] can also be proved easily against the specification. So if we start with nothing, we know only true. Then we create an empty object so we get that empty set. We create an insert object, so we get the fields of this insert object and we can make it more abstract by summarizing this as set2.set. So we're folding up the set predicate for set2. So this is the body of the set predicate for set2, and this is if we followed it up. And then the same for set3. And now if we split this into to two halves, we can call it with itself. So here this program doesn't terminate, but it's safe, so it's partial correctness position holds. So we can prove it with partial correctness, even though this program doesn't terminate. Or actually it does terminate. Sorry. So in this case it terminates because empty and insert, they terminate. So the syntax of assertions is as follows. You have points to assertions, predicate calls, separating conjunction, existential quantification, and the Boolean constants. The method definitions look as follows. So you have a method header with a return type and parameter types. It requires some precondition and it ensures some post-condition. And it has a body. So what is the meaning of assertions? The meaning of assertions is given as follows. It's given in terms of an interpretation I for predicates and then in heap H, which has the contents of all the fields. So the points to assertion or [indiscernible] is true, if the tuple O comma F comma V is in the heap. That is if O.F is in the domain of the heap and the heap maps [indiscernible]. A predicate application is true if the application is in I, is in the interpretation of the predicates. So I is a parameter here. We will see later how we can define a specific interpretation for predicates. But here it's just delegated to I. And then separating conjunction is defined as follows. P1 star P2 holds in heap H if you can split the heap into H1 and H2. So this means that H1 and H2 split the heap up and P1 holds for H1 and P2 holds for H2. >>: What is I? Bart Jacobs: So I is the interpretation for predicate applications. It's a set of tuples of this form. And we will see later how we define a specific I. So if I give you the set of all predicate applications that are true, then I can define the meaning of arbitrary assertions that contain predicate applications. We will see later how we actually define a particular I. >>: It seems that you want both I and H to be functional so that you cannot have OFV in [indiscernible] and also have OFV prime for a different time. Bart Jacobs: Yes, that will always be the case too. That's right. So H is actually going to be a function from ode of F pairs to values. Or actually a partial function. So this is actually just short time for saying ode of F is in the domain and ode of F is mapped to V. >>: H is a partial? Bart Jacobs: That's right. >>: [Indiscernible]. Bart Jacobs: Well, it's a partial map from object reference fields name pairs to values. So you can have one field of an object being in the heap and another field of the same object not being in the heap. That would be useful later. So heaps actually represent permissions to access parts of the full heap. So this might be the full heap or this might be a partial view of the heap in a sense. So part of the heap. And actually we'll use this theorem, right, with the separating conjunction. What you're doing here is you're interpreting P1 in one part of H and P2 in the another part of H. So if P1 talks about one field of object, P2 should not talk about the same field, but it can talk about another field of the same subject. That's the core notion of separation logic. >>: [Indiscernible] Bart Jacobs: Not really. So I will also, so this predicate, they can also talk about the heap. Because note that H is passed in, so it's part of the tuple that is either in or not in I. So the predicates that bundle up a number of points to assertions, then bundle up a part of the heap. >>: [Inaudible] Bart Jacobs: No, not applicable just because this one is in I doesn't mean that it's going to be in I if you take a smaller H. >>: So I is just constant, it doesn't change as the program executes. Bart Jacobs: That's right. It doesn't change. >>: The partition of all of the predicates. And H keeps changing. Bart Jacobs: Yes. >>: What about local variables? Bart Jacobs: Local variables are not in here. So I is not, it cannot mention to, a predicate definition cannot mention a local variable. >>: But I'm wondering is why, to the left of the turnstile you have I and H, and I was also expecting that you have the valuation of local -- Bart Jacobs: Right. Typically you have that, but here I'm using a programming language that doesn't have mutable local variables. So you have read-only local variables. So I assume the values are substituted into the assertions already. That simplifies some things. But yes in general typically in the literature you have this. All right. So this is the meaning of assertions. Now, how do we mean, how do we define the meaning of predicates? Well, as follows. Given if we already have an interpretation I for predicates, we can define a new interpretation F of I as follows. So we look at the definition of the predicates. So suppose we want to know if this tuple is in F of I. So then we will look at what is the class of object O. If it's class C, then we will look at the definition of predicate B in class C. Suppose this is the definition of that predicate there. And then we will interpret this body under I and H. And if that's true then this tuple is in F of I. So we will use I for the recursive calls in the body of the definition. And given this functor, so to speak, you can take the least-fixed point. So this is the least-fixed point of F, which is well defined, because if is monotonic. So that's the [indiscernible] theorem. Just so we will have the property that F of I fixed equals I fixed. So that means that it satisfies the equations that are the predicate definitions. So that means that we can use this S, a reasonable interpretation for predicates. And in further slides will just write this to mean this. So I fixed will be implicit from now. >>: Does this have something to do with, I think a restriction that you said earlier where predicates appear inside definition of predicates only in positive form? Bart Jacobs: That's right. Yes. That's a crucial restriction to make this work. >>: Okay. Bart Jacobs: So here in this syntax, you will see that there's no negative constructs, so there's no implication or negation in the syntax of assertions, which [indiscernible] trivially that the predicate calls occur only in positive positions. >>: [Indiscernible]. Bart Jacobs: Right. No you can use this for Boolean expressions. So there are Boolean operators. So if you have something that evaluates to either true or false, if you substitute in the local variables, then that's fine. So that's allowed. You can have false in here; right. But you can't have a negation or implication that has either points to assertions or predicate calls in its operands. >>: So essentially you have [indiscernible] existential variables are like [indiscernible] at top level. Bart Jacobs: Maybe, yeah. >>: And what you said would be the case [indiscernible]. Bart Jacobs: Later I will see a way that you can still have negative effects about predicates in VeriFast, just not in this particular talk. In the next part of the talk, the second chapter, so to speak, I will show how you can use ghost commands as proofs of negative effects. So you will have nested triples [indiscernible] about ghost commands, and they can have a predicate in the precondition. And in a sense the precondition of a triple is a negative position. So in that way you can still have negative effects, so to speak, about predicates. >>: Can I just ask for clarification? Bart Jacobs: Sure. >>: Some of these restrictions I imagine you're inheriting because you use separation logic. And some of these restrictions are probably because you want this well-foundedness definition of your [indiscernible] predicates. Am I correct? Bart Jacobs: Well, so the restriction of positive positions is because I wanted to make this fixed point. Because I want this ->>: But you also said something that [indiscernible] logic arrow thing you cannot here or there or something, right? Bart Jacobs: The magic wands, I don't have it here. >>: But that points to your problem that the points cannot appear in some situations? Bart Jacobs: Right. So the points that I just said, it also cannot appear in negative positions here in this case. Actually that's not even needed to be able to make this ->>: Why do you have this restriction? Bart Jacobs: Actually, I've never needed negative points in any proofs. So I don't think it's particularly useful. >>: Okay. Bart Jacobs: All right. So now how do we express the semantics of programs? And you can use coinductive semantics for that. So if you run a command in a particular heap, it will have an outcome, or it can have one or more outcomes. And an outcome is either successful termination in N steps with result value V and final heap H or failure in N steps or divergence. And we will define the notion of adding a natural number to it with an outcome. So if it's a successful outcome, then you just add it to the number of steps. The same for failure, adding a number to divergence just means divergence. And given those definitions we can define the meaning of a call as follows. If you perform a call in heap H, then of course you will look up the method body. And the body will depend on the parameters X. You will look up the method body in the class of [indiscernible], which is C. And then if you execute this body with the arguments substituted for the parameters then you get some outcome. So the outcome of the call is going to be 1 plus the outcome of the body. And this should be interpreted as a co-inductive rule. So with double lines. And that means that if you have infinite recursion, if the body of M is itself a call of N on the same object, then the only possible outcome is divergence. Right? If I didn't have this 1 plus, by the way, if I just had 0 here, then all possible outcomes would be allowed by the semantics. And that's why I have this 1 plus so that you can only have divergence here. If it's an infinite recursion. All right. So given that we have a notation for the meaning of programs, how do we define the meaning of a triple? So that's defined as follows. So for partial correctness, if the precondition, so for any heap where the precondition holds, and if we get an outcome O, then either O is divergence, or we have a successful termination such that the post-condition holds in the post-heap with the result value substituted in. It's like the obvious meaning; right? So a triple is true if you start in any state that satisfies a precondition, running the commands will end up in the state that satisfies the post-condition. That's just what it says there. Nothing else. [Indiscernible] allow divergence here. Because this is a partial correctness semantics or proof system. So then we want to prove it. If we can prove a triple using the proof rules, then it is true. And I didn't show any proof rules here. But they are standard. So then how do you prove this theorem? Well, you will prove it by induction on the number of execution steps. So you will use the following lemma for all number of steps N given that we reach an outcome O, then it will not be failure in N steps. And if we successfully terminate in N steps, then the post-condition is satisfied. And you can prove it by induction on the number of steps. All right. And then by induction [indiscernible] induction on the derivation on the proof itself. So then how do we extend this logic to deal also with total correctness? What we add is just one thing, a call condition. And it will be in the first approximation, it will be qualified by a natural number. So actually this simply means that we have N, that we have permission to perform N calls here. So given that, the meaning of this assertion, so we will interpret the meaning of an assertion under I and H as before, but also N. So then of course if you claim that you have call permission and N prime, then N prime has to be at least N prime. So you can have additional call permissions, that's fine. Separating conjunction is also interpreted in the obvious ways. So we have to split not just the heap, but also the call permissions into two parts such that P1 holds for N1 and P2 holds for N2. So they are non-duplicatable, of course, these call permissions. And then the proof rule for method calls is extended so that it consumes one call permission at each call. So one call permission is taken out of the program's stock of call permissions at each call and it's gone. And since there are only finite number of call permissions at the start of the program, it follows that the program can only perform finitely number of calls. It's very simple. So how do we prove soundness for this? So the meaning of a triple is exactly as before except that we don't have this disjunct that says that we allowed divergence. That's gone. And then how do we prove that given the proof rules we have that triple is true? Now, we will define it, we will use a lemma that performs induction not on the number of steps but on the number of calls. So N is now the number of calls. And that will also work out. So you now perform induction on the number of calls. And nested, you will also look at the size of the command. So the only command where the size of the command itself doesn't decrease through execution is calls, and calls are dealt with using call permissions. So that's why this is a valid way of doing induction. All right. So now how do we use these call permissions for modular specifications? Because of course what you don't want is to have at the top of main a request for call permissions for exactly the number of calls that that particular program will make. That's not modular. Then you're revealing exactly how many calls you are making throughout a program. So you don't want to do that of course. Consider this very simple program. So we have a main method that calls square root, and square root calls square root helper some number of times. So a naive way to specify to perform modular verification of this program is to say, oh, well, square root helper doesn't perform many calls itself, so we're going to request call permission of zero. So that's just the same as true really, because we're not requesting anything. Square root, it performs two calls, so it requests call permission of two. And then main calls to square root, which itself needs two call permissions, so main needs three call permissions. Right? Okay, so that's a valid spec, a valid proof. But it's not modular. It reveals information that we want to hide. So how do we do that? And the way to do that, actually one other problem is that it becomes very tricky to specify ackermann. So if you want to have a crash clause for this main method that cause ackermann, the ackermann function, which is a well-known function that performs a lot of recursion, then you will need to specify the number of calls that are performed by ackermann. And that's quite tricky. You can do it, but it's not something you want to do. So you want to be able to abstract over the precise number of calls that are made. So of course I talk about ackermann, you'll hear me coming, I'm going to talk about ordinal numbers, about a lexicographic ordining. So in general we will look at well-founded orders. And an order is well-founded, well I'm sure all of you know, but let's just quickly recall. So a relation is not necessarily even an order. A relation is wellfounded if all non-negative, non-empty subsets of the domain of R have a minimal element. So for each non-empty set X, there is some element of X such that no other elements of X is less than it, or no element of X is less than X. That's minimal of course, minimality. And that's the same as saying that there are no infinite descending chains of R. Of course the natural numbers are well-founded. We can define well-foundedness. Given two well-founded relations, we can define the lexicographic composition of these relations. And here we will assume that the second element is the most significant one. So either the second component of the pair decreases or the second one stays the same and the first one decreases. So then we consider this pair to be less than this pair. So 10 comma 2 is less than 1 comma 3. And the other thing that you could do is multi-set order. So this is denoted as follows. Omega to the X. That means order over sets, over multi-sets of elements of X. And then the order is defined as follows. You can get from one multi-set to a smaller one by removing any element and replacing it with any number of smaller elements. That's how you move down in the order over the multi-sets. This is what we will use also in our proofs. So we will not use just natural numbers, but ordinals to qualify call commissions. And so we will interpret assertions under IH and lambda, where lambda is multi-set of ordinals. So each element of lambda is a call permission qualified by some ordinal. So call permission of alpha holds under some lambda if alpha is an element of lambda. Separating conjunction is interpreted as follows. We have to be able to split lambda so that one part satisfies P1 and the other part satisfies P2. We will also allow the program to perform ghost steps that weaken lambda. So if you have a certain stack of call permissions, you can replace any of those call permissions by any number of lesser call permissions. And that is denoted by this square subset symbol. So we can go from a state satisfying assertion B to one satisfying assertion B prime, if B prime is satisfied by a smaller lambda than B. So for example we then have the following loss or facts. Call permission of 1 can be weakened to call permission of zero star call permission of zero. And another example is call permission of 01 can be weekend to call permission of 10,0 and call permission of 20,0. Call permission of multi-set 3 can be weekend to call permission of 2,2,2, and call permission of 1. Because both of these multi-sets are smaller than this multi-set. >>: Is this the lambda prime lesser equal to [indiscernible]? Bart Jacobs: Lesser equal. This prime is the same one. It's the ordinary implication of assertions. So this is one assertion implies one other assertion, right. There's no star there. So how do we now use this to abstractly specify this program? And the trick here is to use method names as ordinals. So we will interpret method names, we will define an order on method names. Simply by the textual order of the program. So we will say square root helper is the minimal element, and square root is greater than square root helper, and main is greater than both square root and square root helper. And using this order, we can use, we can define call permission as follows. So we will use the method names as the set of ordinals. So main and all programs that you will see from now on, main will always require simply a call permission for itself for program.name. So the specification for the main method doesn't reveal anything about what the program does internally, how many calls it makes. It always simply requires main. Since main calls square root it needs, and square root itself, so by the way, more generally, any method will simply require a call permission qualified by its own name. So square root will require a call permission of square root. Square root helper will require a call permission of square root helper. So if main wants to call square root, it needs to weaken its own call permission to two call permissions over square root. Why two? One of them is consumed by the call itself. So remember the proof rule that we saw before? A call is valid if the precondition holds and separately we have an extra call position which is consumed and lost forever. So that's why main has to be weakened to two instances of square root. One of them is lost and the other one is passed into the body of square root to satisfy its precondition. And square root itself will also weaken its own permission to two copies of square root helper. And then square root helper will simply not use its call permission, but that's fine of course. So now how do we verify and specify ackermann? Ackermann itself, so the general rule is that if you have recursive functions or recursive methods, they should be internal to a module. So that means that the specification of the recursive method itself doesn't have to be abstract. It is okay for it to reveal the structure of the implementation, to be implementation specific. So what you then do is you have a separate method, a separate public method that is, that has an abstract specification. So ackermann itself requires a simply math ackermann. Here we have given a little bit more structure to the call permissions, so we are not using just method names, but also local ranks. So now the ordinals used to qualify call permissions are pairs of local ranks and method names. And the local ranks are the usual kinds of ranking functions that you use to verify termination of recursive methods. Yes? >>: Does this mean when I implement an interface I need to respect the order of the interface methods, how they were declared in the interface? Because in my implementation they're going to have to be in the same order to get the same ordinals? Bart Jacobs: Yes, but that can easily be -- you can easily work around that by having the implementations of the interface methods call into private, other private methods that are above all the other interface implementations. >>: [Indiscernible]. Bart Jacobs: They are all less than the implementation methods, and therefore you can call any of the implementation methods in any of the interface methods. So you simply duplicate all of the methods and it's not a problem. So it's not a fundamental restriction that way. So how do we then verify ackermann? So ackermann itself and the general rule will be public methods simply require a call permission qualified by 0 comma, its own name. So this doesn't reveal any information about the contents. And then the recursive method will have a call permission for ackermann and it's the local rank, the rank that is appropriate for ackermann, namely the two arguments lexicographically sorted. So ackermann weakens its own permission to MN ackermann iter, which is less than ackermann, because the second component is the most significant one. Ackerman iter is less than ackermann. So it can choose anything for the local rank when it performs this weakening. Okay, then the really hard part is dynamic binding. Yes? >>: [Indiscernible]. Bart Jacobs: Mutual recursion is done by, so each of these mutually recursive methods will request permission for the bottom one or the greatest one. In the paper there's an example like that. So how do we do dynamic binding? So suppose here I have now another example that uses dynamic binding. So we have an interface for real function so you can apply to a double to get another double. Then we have a method, the static methods that performs integration, that computes an approximation of the integral over F between some bounds A and B. So it's going to have to call F a number of times. How do we specify these methods? So here's a first attempt. Actually, no, this is just a partial correctness specification that like we saw before. So we define a predicate in real func and apply will require this predicate. It doesn't actually give anything back because since this is just a reflection of permission, the caller can always keep another reflection for further calls. So it doesn't have to give anything back. Integrate will require a fractional permission for F. So in the partial correctness setting, this is sufficient. This allows integrates to call any number of times. Now, if you also want to prove termination, how do we do this? Of course apply will want to be able to perform static calls of things declared earlier, like we saw before, like square root needs to be able to call square root helper. So by the ordinary rule that we saw before, apply should request a call permission for 0 comma, its own name. And then so this dot apply means actually a class of this dot apply. So this is the name of the specific apply method that implements that interface method. So this is not one name for the interface method itself, this is the name for the class method that implements that interface method. So then integrate, should, by the ordinary rule that we saw before, it should request call permission for itself so that itself can call methods before it, but also call permission for F dot apply. However, this is not sufficient. Specifically consider the following implementation of integrate. So integrate will call a recursive helper function that recursively applies F to some value between A and B, and then recursively calls itself again, right, as you would expect. Now, the problem is what we said here, this spec allows F dot apply to be called only once. Because F dot apply, so if you go back to the spec, F dot apply requires this full call permission that we're requesting in here. So F to the first call that will be completely consumed. So this is not sufficient. The specifically integrate iter will need N copies of F dot apply. For N being the recursive argument here that is descending during the recursion. So how do we fix that? And of course we could just put like omega here, the first infinite ordinal. But if you, but that restrains the implementation. For example, if you want to call this inside of ackermann, then you don't need omega, you would like something bigger than omega. So can we put the biggest ordinal? But there is no such thing as the biggest ordinal. So there's no good modular way to fix this without again using method names. And I will show how. So the way to do that is to not use local ranks, pair of local ranks and method names as ordinals, but local ranks and bags of multi names, so multi-sets of method names. So you do it as follows. Integrate, so for F dot apply itself, we simply require the singleton multi-set of this dot apply. But for integrate we will require the multi-set containing two elements, integrate itself, and F dot apply. And this allows integrate to weaken this permission to an arbitrary local rank when it goes from integrate to integrate iter. So when you weaken integrate F apply to integrate iter F apply, you can choose an arbitrary local rank. And this allows you to perform this call. And here you can perform any number of calls that you need. Now this still doesn't, now we still haven't achieved a fully general specification. Because F dot apply can only perform static calls here given this permission. It cannot perform any dynamically bound calls on objects that it prefers to by its fields. So if it has a reference to another object, then it cannot call that object because it doesn't know the methods of that object are statically below itself. So here too we need to pass not just this dot apply but some arbitrary extra set of method names. So this is an example of an implementation of real func calls into other objects. So this real func defined as the sum of two other real funcs, when you apply it, it will call F1 dot apply plus F2 dot apply. So that one isn't supported by the [indiscernible] that we just saw. So how do we fix that? So this by the way is the partial correctness specification of the sum class. So it's predicate real func will have the permission to access field F1 and it will have the real func predicate for F1. It will also have permission to access F2, and the real func predicate for F2. And I said, this is not going to be enough to verify this body. What we then do is we expose from the real func predicates the bag of methods reachable by the object. So each object will expose to its clients abstractly the bag of methods reachable from it. This in itself sounds very breaking of information hiding, but it isn't because we're not saying anything about what this bag is, we just allow the client to name it, just allow it to be mentioned in specifications, but we're not going to say anything about what is in there and what is not in there. So the spec for apply now becomes as follows. We have just as for partial correctness we have a flexible permission on this dot real func, and now we bind this method bag D and then we say we have call permission for all of those methods D. So this is multi-set. This will of course include apply itself. So for the sum we define it as follows. >>: [Indiscernible] it's a logical variable? Bart Jacobs: Yes. That's right. So how do we define the predicate for some example? So as before, sum needs to have access to F1 and to the real func of F1. And now we name the method bag of F1 and D1. We name the method bag of F2 as D2, and our own D is going to be our own apply methods, multi-set union D1, multi-set union D2. And this way apply can call all of the methods of F1 and F2 and it can apply statically higher methods of itself. So now the proof is easy. Because thanks to the fact that both D1 and D2 are smaller than D, because you can at least move this element. You can weaken this call permission to all of the call permissions required for the recursive calls. So this becomes the general specification for real func. And specification for integrate now becomes as follows. We need the real func predicate for F. It will have some bag of methods D and we need call permission for ourselves to integrate multi-set union D. So these are actually all of the methods reachable by integrate. This represents the method statically above integrate and this represents the methods reachable through the F object. So we can prove the implementation of integrate as follows. Integrate iter will require a call permission local rank N and method bag integrate iter union D. So how do we use, what is some client code that uses this specification? So we have a program main that will perform the integration of some linear function defined by some coefficient A and some constant B. So it will create a number of those linear functions and then will create a sum object of those linear functions, and then will call integrate. So how do we verify total correctness of this client program? At last linear itself will have to have implementation of the real func predicate. It's easy. So it has to have access to A and B. And its own method bag will simply be its own apply methods. And then the proof outline for the main function looks as follows. So main simply requires call permission for itself. So we have, if we create a linear object, we get a real func with method bag linear dot apply. We create another one, so we have also real func for linear dot apply for F2. Then we create a sum. We also have a real func for some dot apply and two copies of linear dot apply. And we can then weaken the call permission for main to two copies of call permission for sum dot apply and two times linear dot apply, that allows us to perform this integrated call. >>: So does this work because all of these limitations know statically how many method calls they need to make? Suppose it was instead of a linear function, something that, some dynamic structure and it needed to call as many as the [indiscernible] in its dynamic chain. Bart Jacobs: Right, but isn't that like the sum object? So sum is built from linear objects and it doesn't know itself and its built from linear object? >>: [Indiscernible]. Bart Jacobs: Well, they can themselves be again some [indiscernible] for example. >>: Yeah, that's true. Bart Jacobs: Right, this is actually I think the end. Well, actually there's one more problem remaining. Here I know what happens at construction time, so I also want to abstract over what happens at construction time. I can do it as follows. So what does a constructor function or a constructor methods for linear look like? It will require a call permission for itself simply. Actually I forgot the zero here. And it will say I give you an object that satisfies the real func predicate for sum D, and D will be less than myself. And thanks to this information, so here we don't know the exact value of D but we have an upper bound for it, and that allows the main function to weaken its own call permission to a call permission for D. And similar for sum. So create sum will require the usual call permissions for F1 and F2. And it will give back an object whose method bag is less than create sum union D1, union D2. So that's all I wanted to say about modular termination verification. And I see now that it's been an hour already. So I guess the other topics we should leave for some other time. Thank you very much. [Applause] Rustan Leino: Those other topics, you're free to ask questions for about, and Bart is here today and tomorrow. So if you want to chat with him with those [indiscernible]. Any other questions? >>: Your examples of functional language [indiscernible] allocating [indiscernible]. Is that all that's required? Bart Jacobs: No. So the logic fully supports mutation. And I mean I don't see at this point any reasons why the specification style also should not support mutation. For example, these predicates, it's perfectly fine for them for the arguments to change as you mutate the data structures. >>: But at some point mutation, you get in trouble if you [indiscernible]. Where does that show up? Bart Jacobs: So actually this already shows you how circularity would be shown. So actually if you create a circular list, then I would say that you know that you're doing that. So it would be within one module. And then you know how many elements it has. So I think it wouldn't be a problem. >>: [Indiscernible]. Bart Jacobs: Yes. >>: I mean what if you [indiscernible]? Bart Jacobs: Right. Right. Of course it's ruled out. I mean this is sound, so I can prove the program. The question is then if it doesn't terminate, then you cannot prove it. Right. So what do you still want to prove about it. That's the question I guess. >>: I guess my question is more how does that [indiscernible] what is the specific problem I run into [indiscernible]. Bart Jacobs: So it's hard to tell. It's going to fail. I guess I would have to try it to really be able to answer that. But note that of course these multi-sets, these bags are finite things. So they will have trouble to create an infinite search bag. So you would have to, during creation they would have to have some well-founded process to come up with these bags that would be impossible, I guess. >>: [Indiscernible]. Bart Jacobs: So this is for proving termination, right? So does that program terminate? >>: Well, I guess not [indiscernible]. Bart Jacobs: Then you cannot verify. You have to know that it's terminating, right, otherwise you cannot verify the [indiscernible]. >>: I suppose you're not shooting for maximum mobility, which allows to you prove anything more. Bart Jacobs: [Laughter]. >>: It actually verifies like that too. Bart Jacobs: [Laughter]. Rustan Leino: So Bart will be around. [Applause]