├── .gitignore ├── Readme.md ├── example.rs ├── lifetimes.dl └── printstrings.dl /.gitignore: -------------------------------------------------------------------------------- 1 | *.csv 2 | .vscode 3 | .idea 4 | cmake* 5 | CMakeLists.txt 6 | 7 | # OS Specific 8 | Icon? 9 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | This is a lifetime inference algorithm for [Rust](https://www.rust-lang.org/) written in [Soufflé](https://souffle-lang.github.io/). It is a proof of concept and will not actually work with your source code automatically—but it could in principle if we hooked it up to Rust’s AST. The To Do section below illustrates how unnecessarily restrictive the Rust compiler is regarding lifetimes. 2 | 3 | # Usage 4 | 5 | Since it’s a proof of concept, it’s very unsophisticated. In “real life,“ the input comes from the AST generated by the compiler. In this script I just manually typed the input into the script itself. The input is labeled as such. 6 | 7 | The inference algorithm is just a Soufflé script, so the only dependency is Soufflé. Once Soufflé is installed, run the script like so: 8 | 9 | ```bash 10 | $ souffle -D. lifetimes.dl 11 | ``` 12 | 13 | What’s nice about Soufflé is that it will tell you how it deduced something. Here’s how: 14 | 15 | ```bash 16 | $ souffle -D. -t explain lifetimes.dl 17 | > explain <> 18 | ``` 19 | 20 | The `-t explain` flag drops you into a command line. Type `explain` followed by the thing you want explaining. I got tired of doing this, so I rewrote my “error” predicate to tell me where it comes from together with an `error_msg` that produces a human readable error message string. 21 | 22 | ```prolog 23 | .output error_msg 24 | ``` 25 | 26 | # Lifetime Inference in Rust 27 | 28 | Last Monday I decided to take a look at [Rust](https://www.rust-lang.org/) for the first time. Rust has the notion of [lifetimes](https://doc.rust-lang.org/book/ch10-03-lifetime-syntax.html) which is a mechanism to prohibit errors like [use after free](https://www.owasp.org/index.php/Using_freed_memory) and [race conditions](https://en.wikipedia.org/wiki/Race_condition). However, the Rust compiler requires the programmer to figure out the lifetimes manually. Programmers new to Rust find the “borrow checker,“ the component of the compiler that checks lifetime correctness, to be a challenging learning curve. The internet is full of people “[fighting the borrow checker](https://m-decoster.github.io/2017/01/16/fighting-borrowchk/).” 29 | 30 | A lifetime is a type. The Rust compiler infers types, but it doesn’t infer lifetimes.[1](#f1) Why not? I don’t know. It seems like such a thing would be a priority considering how big of a barrier it is to new users. I decided to write my own lifetime inference engine. 31 | 32 | ## How does it work? 33 | 34 | It works the same way type inference works—literally. A lifetime is a type. Consider this [example from The Book](https://doc.rust-lang.org/book/ch10-03-lifetime-syntax.html#lifetime-annotations-in-function-signatures): 35 | 36 | ```rust 37 | fn longest(x: &str, y: &str) -> &str { // ━━━━━━┱───┐ -x and y borrows here. 38 | if x.len() > y.len() { // x's borrow S4┃ S5│ 39 | x // overlaps with ┃ │ -loan_if = x, x drops. 40 | } else { // y's borrow ┃ │ 41 | y // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╋───┤ -loan_if = y, y drops. 42 | } // either x or y is still borrowing here ┃ │ 43 | } // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┹───┘ -result borrows here, while 44 | // x and y go out of scope. 45 | fn main() { 46 | let string1 = String::from( // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ -string1 born 47 | "long string is long"); // string1's scope S1┃ 48 | let result; // ┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉╂┉┉┐ 49 | { // result's scope, but not assigned to, so no lifetime yet ┃S4┋ 50 | let string2 = String::from("xyz"); //─────────────────────┐ ┃ ┋ -string2 born. 51 | result = longest(string1.as_str(), // string2's scope S2│ ┃ ┋ 52 | string2.as_str()); // ┄┄┄┄┄┄┄┄┄┄┄┄┄┄┼┄┄╂┄┄┼┄┄┄┐ -string1 or string2 borrowed here. 53 | } // ──────────────┘ ┃ ┋ S3┆ -string2 dies. 54 | println!("The longest string is {}", result);// - S5 ┃ ┋ ┆ -result drops. 55 | // ━━━━━━━━━━━━━━━━━┹┉┉┘┄┄┄┘ 56 | } // - S6, global scope. 57 | ``` 58 | 59 | The boxes labeled with `S#` represent either lifetimes or regions in which a reference holds a “loan,” which is what I will call a borrow when I need a noun to refer to the abstraction of a reference holding a particular heap-allocated data element. 60 | 61 | ## Program input 62 | 63 | The sourcecode data needs to be input into the program. In particular, the input consists of: 64 | 65 | 1. The [control flow graph](https://en.wikipedia.org/wiki/Control-flow_graph). The *control flow graph* tells us whether program control can flow from point A to point B during execution. The points are program statements, which we identify with line numbers in the source code for simplicity. We write `CFG.edge("L3","L4”)`{:.rust} to mean control flows from line `L3` to line `L4`. The control flow graph itself can be constructed by computing reachability along edges, but we don’t compute it directly, so CFG is only an abstraction. What we actually construct is the *data flow graph* in our application, because it describes whether a piece of data's lifetime, via borrowing or scope, is "[live](https://en.wikipedia.org/wiki/Live_variable_analysis)" at any given point. 66 | 2. Where a loan is created together with some kind of identifier. We write `object_born("string1", "L10")`{:.rust} and, for simplicity, use the name of the original variable that created the loan as the name of the loan. 67 | 3. Where the loan dies, i.e. where it’s owner drops the loan. We write `object_dies("string1", "L20")`{:.rust}. 68 | 4. Where which reference borrows from which other reference. We write `borrows("loan_if", "x", "L3”)`{:.rust}. Note that which loan is borrowed is unnecessary—that is inferred by the algorithm. 69 | 5. Where which reference releases their loan, e.g., because it went out of scope. We write `drops("x", "string1", "L3”)`{:.rust}. 70 | 71 | ## Computations 72 | 73 | From the program input, we compute the set of all points a given referenceis borrowing a given loan. The reference starts holding the loan exactly when it borrows it: 74 | 75 | ```prolog 76 | borrowing(ref, ln, p) :- borrows(ref, ln, p). 77 | ``` 78 | 79 | It continues borrowing the loan along the control flow graph until it drops the loan, perhaps by going out of scope: 80 | 81 | ```prolog 82 | borrowing(ref, ln, p2) :- CFG.edge(p1, p2), 83 | borrowing(ref, ln, p1), 84 | !drops(ref, ln, p2). 85 | ``` 86 | 87 | This computes the region of source code that the reference `ref` holds the loan `ln`—sort of: crucially, despite the fact that I’ve called `ln` a loan, it is actually *a reference to a loan.* So `borrowing` tells us which reference is borrowing from which other reference at which points in the source code. But which loan is it referencing? 88 | 89 | To bootstrap, we declare (perhaps awkwardly) that the loan’s owner references itself. (Recall that we are naming the loan after its owner, so it makes a bit more sense to read “references” as, “is held by” or something.) 90 | 91 | ```prolog 92 | references(var, var, pt) :- object_born(var, pt). 93 | ``` 94 | 95 | Next, whatever reference is borrowing the loan, we need the reference to propogate to the next point on the control graph (the next statement) if it’s still borrowing from the same thing: 96 | 97 | ```prolog 98 | references(ref2, ln, pt) :- CFG.edge(p1, p2), 99 | borrowing(ref2, ref1, p1), 100 | references(ref2, ln, p1), 101 | borrowing(ref2, ref1, p2). 102 | ``` 103 | 104 | The crucial difference between `references` and `borrowing` is, references doesn’t update its referrent. That is, the thing being referred to never changes from when it came into being at an `object_born`: 105 | 106 | ```prolog 107 | references(ref2, ln, pt) :- CFG.edge(p1, p2), 108 | borrowing(ref1, _, p1), 109 | references(ref1, ln, p1), 110 | borrowing(ref2, ref1, p2). 111 | ``` 112 | 113 | ## Detecting Errors 114 | 115 | Conceptually, if `ref2` borrows from `ref1`, then we need the set S2 := \{ `p` ∈ Program | `borrowing(ref2, ref1, p)` \} to be a subset of the set S1 := \{ `p`  | `borrowing(ref1, _, p)` \}, that is, S1⊂ S2 . Said another way, the lifetime of `ref2`'s borrow must be a subset of the lifetime of `ref1`'s borrow.[2](#f2) 116 | 117 | A dangling pointer occurs when the loan dies while something is still referencing it, perhaps because its owner goes out of scope. From the conceptual perspective of sets, an error occurs when we find a point `p`, a reference `ref`, and a loan `ln` such that `references(ref, ln, p)`{:.prolog} but NOT `borrowing(ln, ln, p)`{:.prolog} (the owner drops `ln`). In symbols, S1⊂ S2 is a constraint, but `p` ∈ S1∖ S2, contradicting the constraint. To be fancy, we also compute the point at which the reference first borrows the object. 118 | 119 | ```prolog 120 | error_use_freed(var, ln, errpt, borpt) :- references(var, ln, errpt), 121 | object_dies(ln, errpt), 122 | references(var, ln, borpt), 123 | borrows(var, _, borpt). 124 | ``` 125 | 126 | It is also an error to have distinct (mutable) references to the same object. I don't actually make a distinction between mutable and immutable borrows, so this error isn't true to life. It is just to show how it would work. Again, we're fancy and compute the reference already pointing to the object and where that reference borrowed from the object. 127 | 128 | ```prolog 129 | error_already_borrowed(ref1, ln, errpt, ref2, borpt) :- references(ref1, ln, errpt), 130 | borrows(ref1, ln, errpt), 131 | references(ref2, ln, errpt), ref2!=ref1, 132 | borrows(ref2, ref3, borpt), ref2!=ref3, 133 | references(ref2, ln, borpt). 134 | ``` 135 | 136 | Et voilà! 137 | 138 | ``` 139 | Error: 'string2' is dropped at L17, but 'result' still holds the loan it borrowed at L15. 140 | ``` 141 | 142 | And that’s pretty much it, modulo a few details you can find in the file `lifetimes.dl`. From here it’s not too hard to compute lifetime constraints, for example, `lifetime(ref2)` ⊂ `lifetime(ref1)`. 143 | 144 | # Mathematical Points 145 | 146 | * The body of a function completely determines the most general lifetime annotations of the function signature. 147 | * The lifetimes in a function signature affect the code at the call site but not the other way around. In other words, lifetimes are synthesized from the bottom up. 148 | * Lifetimes *are* types, and lifetime inference *is exactly the same as type inference.* To determine the most general lifetime annotations for a function signature, one just unifies the lifetimes of the return value and program arguments. 149 | * I suspect lifetime inference is complete and can be done in linear time. 150 | 151 | # To Do 152 | 153 | The last three items below demonstrate how much the Rust compiler could do that it currently isn’t doing. The Rust compiler is far more restrictive than necessary. 154 | 155 | 1. Make a distinction between mutable and immutable borrows. 156 | 2. Better examples of multiple mutable borrows. 157 | 3. Is it easy to hook this into a Rust parser? If so, do it. 158 | 4. Generate inferred lifetimes. 159 | 5. Allow a (mutable) borrow if the previous (immutable) borrow will no longer be used. 160 | 6. Allow a borrow after a loan die if the borrow will no longer be used. 161 | 162 | ---- 163 | 164 | 1: This is a convenient lie. [↩](#a1) 165 | 166 | 2: This is also a convenient lie. [↩](#a2) 167 | 168 | --- 169 | 170 | # License 171 | 172 | Created by Robert Jacobson on 16 June 2019. I consider this too trivial to need a license, but I’m giving it the MIT License just in case someone needs one. The example Rust source code is from The Rust Programming Language, which is part of The Rust Project. The Rust Project is dual-licensed under Apache 2.0 and MIT terms. 173 | 174 | Copyright (c) 2019 Robert Jacobson. 175 | 176 | The MIT License 177 | 178 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 179 | 180 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 181 | 182 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /example.rs: -------------------------------------------------------------------------------- 1 | fn longest(x: &str, y: &str) -> &str { // ━━━━━━┱───┐ -x and y borrows here. 2 | if x.len() > y.len() { // x's borrow S4┃ S5│ 3 | x // overlaps with ┃ │ -loan_if = x, x drops. 4 | } else { // y's borrow ┃ │ 5 | y // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╋───┼ -loan_if = y, y drops. 6 | } // either x or y is still borrowing here ┃ │ 7 | } // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┹───┘ -result borrows here, while 8 | // x and y go out of scope. 9 | fn main() { 10 | let string1 = String::from( // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ -string1 born 11 | "long string is long"); // string1's scope S1┃ 12 | let result; // ┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉╂┉┉┐ 13 | { // result's scope, but not assigned to, so no lifetime yet ┃S4┋ 14 | let string2 = String::from("xyz"); //─────────────────────┐ ┃ ┋ -string2 born. 15 | result = longest(string1.as_str(), // string2's scope S2│ ┃ ┋ 16 | string2.as_str()); // ┄┄┄┄┄┄┄┄┄┄┄┄┄┄┼┄┄╂┄┄┼┄┄┄┐ -string1 or string2 borrowed here. 17 | } // ──────────────┘ ┃ ┋ S3┆ -string2 dies. 18 | println!("The longest string is {}", result);// - S5 ┃ ┋ ┆ -result drops. 19 | // ━━━━━━━━━━━━━━━━━┹┉┉┘┄┄┄┘ 20 | } // - S6, global scope. -------------------------------------------------------------------------------- /lifetimes.dl: -------------------------------------------------------------------------------- 1 | /* 2 | Created by Robert Jacobson on 16 June 2019. 3 | 4 | Lifetime Inference 5 | Description: An implementation of lifetime inference for the Rust programming 6 | language. 7 | 8 | Copyright (c) 2019 Robert Jacobson. 9 | The MIT License 10 | 11 | Permission is hereby granted, free of charge, to any person obtaining a copy 12 | of this software and associated documentation files (the "Software"), to 13 | deal in the Software without restriction, including without limitation the 14 | rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 15 | sell copies of the Software, and to permit persons to whom the Software is 16 | furnished to do so, subject to the following conditions: 17 | 18 | The above copyright notice and this permission notice shall be included in 19 | all copies or substantial portions of the Software. 20 | 21 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 26 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 27 | IN THE SOFTWARE. 28 | */ 29 | 30 | 31 | // souffle -D. lifetimes.dl 32 | // souffle -D. -explain lifetimes.dl 33 | // explain error("result", "longest", "L17") 34 | 35 | /* 36 | ToDo: 37 | 1. Make a distinction between mutable and immutable borrows. 38 | 2. Better examples of multiple mutable borrows. 39 | 3. Is it easy to hook this into a Rust parser? If so, do it. 40 | 4. Generate inferred lifetimes. 41 | 5. Allow a (mutable) borrow if the previous (immutable) borrow will 42 | no longer be used. 43 | */ 44 | 45 | .symbol_type reference 46 | .symbol_type loan 47 | .type referrer = loan | reference 48 | .symbol_type point 49 | .comp DiGraph { 50 | .decl point(a:point) 51 | .decl edge(a:point,b:point) 52 | 53 | point(X) :- edge(X,_). 54 | point(X) :- edge(_,X). 55 | 56 | // Unused: 57 | // .decl reach(a:point,b:point) 58 | // reach(X,X) :- point(X). 59 | // reach(X,Y) :- edge(X,Y). 60 | // reach(X,Z) :- reach(X,Y),reach(Y,Z). 61 | 62 | // Unused: 63 | // .decl clique(a:point,b:point) 64 | // clique(X,Y) :- reach(X,Y),reach(Y,X). 65 | } 66 | 67 | // ------ Input and Their Fundamental Relations ------- 68 | 69 | // The Control Flow Graph tells us whether control can flow from point A to point B. 70 | // This is better labeled the data flow graph, because it describes whether a piece 71 | // of data's lifetime, via borrowing or scope, is "live" as any given point. The 72 | // points are line numbers in the program. 73 | .init CFG = DiGraph 74 | CFG.edge("L10","L12"). 75 | CFG.edge("L12","L14"). 76 | CFG.edge("L14","L1"). 77 | CFG.edge("L1","L2"). 78 | CFG.edge("L2","L3"). 79 | CFG.edge("L3","L4"). 80 | CFG.edge("L4","L6"). 81 | CFG.edge("L2","L4"). 82 | CFG.edge("L4","L5"). 83 | CFG.edge("L5","L6"). 84 | CFG.edge("L6","L7"). 85 | CFG.edge("L7","L15"). 86 | CFG.edge("L15","L17"). 87 | CFG.edge("L17","L18"). 88 | CFG.edge("L18","L20"). 89 | 90 | // These are inputs and are defined by either the object going out of scope 91 | // or the object being initialized or overwritten. 92 | .decl owner_created(ref:reference, pt:point) 93 | // These are object owners. In real life, we would also track moves from one owner to another. 94 | owner_created("string1", "L10"). 95 | owner_created("string2", "L14"). 96 | 97 | .decl object_born(ref:loan, pt:point) 98 | object_born(cat(refr, "_val"), pt) :- owner_created(refr, pt). 99 | 100 | // Just for convenience: 101 | .decl reference_owns(ref:reference, ln:loan, at:point) 102 | reference_owns(refr, ln, pt) :- owner_created(refr, pt), object_born(ln, pt). 103 | // These are inputs: 104 | .decl owner_destroyed(ref:reference, pt:point) 105 | owner_destroyed("string1", "L20"). 106 | owner_destroyed("string2", "L17"). 107 | .decl object_dies(ln:loan, at:point) 108 | // This would need to be adjusted for moves. 109 | object_dies(ln, at) :- reference_owns(refr, ln, _), owner_destroyed(refr, at). 110 | 111 | // What borrows what at which point? 112 | .decl borrows(ref1:reference, ref2:referrer, at:point) 113 | // Every object owner borrows from itself. 114 | borrows(ref, ref, at) :- reference_owns(ref, _, at). 115 | // borrows("string1", "string1_val", "L10"). 116 | // borrows("string2", "string2_val", "L14"). 117 | 118 | // This is input from AST. Each has either a corresponding `drops` line or a `borrows` 119 | // line in which the reference gives up the loan it has borrowed. We create virtual 120 | // lones for temporaries/rvalues: loan_if is a virtual reference representing the 121 | // rvalue of the `if` statement that is eventually returned. Could have cut it out. 122 | borrows("loan_if", "x", "L3"). // From inside longest, if x returned 123 | borrows("loan_if", "y", "L5"). // From inside longest, if y returned 124 | // The loan longest is a virtual reference representing the rvalue returned 125 | // by `longest`. Could call it something else so longest isn't overloaded, but who cares. 126 | borrows("longest", "loan_if", "L7"). 127 | borrows("result", "longest", "L15"). // Recieves loan from longest 128 | // This is input computed from the call site: 129 | borrows("x", "string1", "L1"). 130 | borrows("y", "string2", "L1"). 131 | 132 | // The object either goes out of scope, is assigned to, or otherwise cannot hold 133 | // its previous borrow any more. The reference `dropping` is dropping it's value at 134 | // location `at`. 135 | .decl drops(dropping:reference, dropped:referrer, at:point) 136 | // An object drops its own "loan" when it dies. 137 | drops(dropping, dropping, at) :- object_dies(_, at), owner_destroyed(dropping, at). 138 | // drops("string1", "string1_val", at) :- object_dies("string1_val", at). 139 | // drops("string2", "string2_val", at) :- object_dies("string2_val", at). 140 | 141 | // An object drops its loan when it is borrowed from. This is the semantics for 142 | // mutable borrows, but it's the more interesting case, so we do this to show how 143 | // it would go. 144 | // ToDo: This... doesn't feel right. 145 | drops(ref1, ref2, at) :- 146 | borrows(ref1, ref2, _), 147 | borrows(_, ref1, at), 148 | ref1!=ref2. // Exclude self borrowing. 149 | 150 | // This is input computed from borrows and references leaving scope. 151 | // Note that we already have a rule built in that says a reference 152 | // drops its borrow if another reference borrows from it. 153 | drops("x", "string1", "L3"). // From inside longest, if x returned 154 | drops("x", "string1", "L7"). // From inside longest, if y returned 155 | drops("y", "string2", "L5"). // From inside longest, if y returned 156 | drops("y", "string2", "L7"). // From inside longest, if x returned 157 | drops("result", "longest", "L18"). // println borrows result 158 | 159 | // ------ Data Flow Graph ------- 160 | 161 | // Variable var is borrowing loan ln at point p. 162 | .decl borrowing(ref:reference, ref2:reference, p:point) 163 | // borrowing(ref, ref, pt) :- reference_owns(ref, _, pt). // bootstrap 164 | borrowing(refr, ref2, p) :- borrows(refr, ref2, p). 165 | // Then propogate to next statement. 166 | borrowing(refr, ref2, p2) :- CFG.edge(p1, p2), 167 | borrowing(refr, ref2, p1), 168 | !drops(refr, ref2, p1). 169 | 170 | // Conceptually, if var borrows ln and ln is a reference, then we need the set 171 | // of all p such that borrowing(var, ln, p) to be a subset of the set of all p 172 | // such that borrowing(ln, _, p). In other words, the lifetime of var's borrow 173 | // must be a subset of the lifetime of ln's borrow. 174 | .decl references(var:reference, ln:loan, pt:point) 175 | references(ref, ln, pt) :- reference_owns(ref, ln, pt). // bootstrap 176 | // Propogate to the next statement. 177 | references(var, ln, current) :- CFG.edge(pred, current), 178 | borrowing(var, ref, pred), 179 | references(var, ln, pred), 180 | borrowing(var, ref, current). 181 | // Propogate across a borrow. 182 | references(ref1, ln, current) :- CFG.edge(pred, current), 183 | borrows(ref1, ref0, pred), 184 | references(ref0, ln, pred), 185 | borrowing(ref1, ref0, current). 186 | 187 | // ------ Error Detection ------- 188 | 189 | // A dangling pointer occurs when an object dies while something is still 190 | // referencing it. From the conceptual perspective of sets, an error occurs 191 | // when we find a point p, a reference ref1, and a loan ref2 such that 192 | // borrowing(ref1, ref2, p) but NOT borrowing(ref2, ln, p) for some loan. 193 | // In symbols, $S1 \subset S2$ is a constraint, but $p\in S1\setminus S2$. 194 | // To be fancy, we also compute the point at which the 195 | // reference first borrows the object. 196 | // ln dies at pt, but var still holds the loan it borrowed at borpt. 197 | .decl error_use_freed(var: reference, ln:loan, errpt:point, ref1:reference, borpt:point) 198 | error_use_freed(ref2, ln, errpt, ref1, borpt) :- 199 | CFG.edge(prevpt, errpt), 200 | object_dies(ln, prevpt), 201 | references(ref2, ln, errpt), 202 | references(ref1, ln, borpt), 203 | borrows(ref2, ref1, borpt). 204 | 205 | // It is also an error to have distinct references reference the same object. 206 | // Again, we're fancy and compute the reference already pointing to the object 207 | // and where that reference borrowed from the object. 208 | // I don't actually make a distinction between mutable borrows and immutable 209 | // borrows, so this error isn't true to life. It is just to show how it would 210 | // work. 211 | // ref1 tries to borrow ln at errpt, but ref2 has already borrowed ln at borpt. 212 | .decl error_already_borrowed(ref1:reference, ln:loan, errpt:point, ref3:reference, 213 | ref2:reference, borpt:point) 214 | error_already_borrowed(ref1, ln, errpt, ref3, ref2, borpt) :- 215 | references(ref1, ln, errpt), 216 | borrows(ref1, ref3, errpt), 217 | references(ref2, ln, errpt), ref2!=ref1, 218 | borrows(ref2, ref3, borpt), ref2!=ref3, 219 | references(ref2, ln, borpt). 220 | 221 | .type String 222 | .decl error_msg(msg:String) 223 | error_msg( cat("Error: '", ref1, "' tries to borrow '", ln, "' from '", ref3, "' at ", 224 | errpt, ", but '", ref2, "' still holds the loan it borrowed at ", 225 | borpt, ".")) :- error_already_borrowed(ref1, ln, errpt, ref3, ref2, borpt). 226 | error_msg( cat("Error: '", ln, "' is dropped at ", errpt, ", but '", ref2, 227 | "' still holds a reference to it, which it borrowed from '", ref1, 228 | "' at ", borpt, ".")) :- error_use_freed(ref2, ln, errpt, ref1, borpt). 229 | 230 | // .output drops 231 | // .output borrows 232 | // .output borrowing 233 | // .output object_dies 234 | // .output error_use_freed 235 | // .output error_already_borrowed 236 | .output error_msg -------------------------------------------------------------------------------- /printstrings.dl: -------------------------------------------------------------------------------- 1 | /* 2 | Created by Robert Jacobson on 18 June 2019. 3 | 4 | Print Strings 5 | Description: A library to create lists from predicates and strings from 6 | lists, and to print strings and lists of strings. 7 | 8 | Copyright (c) 2019 Robert Jacobson. 9 | The MIT License 10 | 11 | Permission is hereby granted, free of charge, to any person obtaining a copy 12 | of this software and associated documentation files (the "Software"), to 13 | deal in the Software without restriction, including without limitation the 14 | rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 15 | sell copies of the Software, and to permit persons to whom the Software is 16 | furnished to do so, subject to the following conditions: 17 | 18 | The above copyright notice and this permission notice shall be included in 19 | all copies or substantial portions of the Software. 20 | 21 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 26 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 27 | IN THE SOFTWARE. 28 | */ 29 | 30 | .symbol_type text 31 | .type NList = [ 32 | head : number, 33 | tail : NList 34 | ] 35 | .type TList = [ 36 | head : text, 37 | tail : TList 38 | ] 39 | 40 | // Print a string. 41 | .decl print(x:text) 42 | .output print 43 | // Souffle can't print lists. 44 | .decl printstringlist(txt:text, lst:TList) 45 | print(cat("[", first, ", ", txt)) :- printstringlist(txt, [first, nil]). 46 | printstringlist(cat(hd, ", ", txt), [i, j]) :- printstringlist(txt, [hd, [i, j]]). 47 | printstringlist(cat(i, "]"), tl) :- stringlist([i, tl]), printpred(i). 48 | //makestringlist([i, tl], [ord(i), _]), printpred(i), ord(i)=max ord(s): printpred(s). 49 | 50 | 51 | .decl ordbiger(i:text, j:number) 52 | ordbiger(s, -1) :- ord(s)=0, item(s). 53 | ordbiger(i, j) :- item(i), item(r), ord(r)=j, ord(i)=min t: {item(s), t=ord(s), t>j}. 54 | 55 | 56 | // Makes a list of strings 57 | .decl makestringlist(lst1:TList, lst2:NList) 58 | .decl stringlist(lst:TList) 59 | stringlist([i, tl]) :- makestringlist([i, tl], [ord(i), _]), listpred(i), ord(i)=max ord(s): listpred(s). 60 | makestringlist([i, nil], [ord(i),nil]) :- listpred(i), ord(i) = min ord(s) : listpred(s). 61 | makestringlist([i,tl], [ord(i), [j, lst]]) :- makestringlist(tl, [j, lst]), listpred(i), ordbiger(i, j). 62 | 63 | // Creates a list of all i satisfying listpred. 64 | // Usage: 65 | // listpred(i) :- item(i) 66 | // Then use stringlist([i,tl]), item(i) wherever you want. 67 | .decl listpred(i:text) 68 | listpred(i) :- printpred(i). // Make a list for all print predicates. 69 | 70 | // Prints a list of every i satisfying item(i). 71 | // Automatically makes listpred(i) 72 | // Usage: 73 | // printpred(i) :- item(i) 74 | .decl printpred(i:text) 75 | 76 | 77 | /// Test predicate: 78 | // .decl item(x:text) 79 | // item("one"). 80 | // item("two"). 81 | // item("three"). 82 | // printpred(i) :- item(i). --------------------------------------------------------------------------------