跳到正文
参考文档 / ZLIB × LEAN

Libc.lean

此页源文件快照 · 7480b41dab4b5e90 · 对应提交的原文 ↗

本页保留 Lean 源码及行号。点击行号可定位语句,点击 import 中的模块名可查看依赖。

1import ZlibF.Runtime.Ptr
2import ZlibF.Runtime.LibcTables
3
4/-!
5# Runtime: the C library
6
7Trusted base of every translation; see `docs/trusted-base.md`. Everything here models a function of the C library
8(or of the compiler's literal handling), not the C language: the language level (pointers, arithmetic, loops, the
9heap tables) is in `Space.lean`, `Monad.lean` and `Ptr.lean`. Each function says how it differs from the real one.
10
11| C library | Lean | Differs from the real function |
12| --- | --- | --- |
13| `memcpy`, `zmemcpy` | `Ptr.memcpy`, `memcpyB` (and `copyK` for a whole record, `LibcTables.lean`) | faults on NULL even for `n = 0`; on a read-only destination (for `n > 0`); on overlap; counts elements, not bytes |
14| `memmove` | `Ptr.memmove`, `memmoveB` | the same without the overlap fault |
15| `memset`, `zmemzero` | `Ptr.fill` (set `n` elements to a value), `Ptr.memset` (every byte of each element is `c`), `memsetB` | counts elements; `Ptr.fill` is `memset` only for bytes or the value 0 |
16| `strlen` | `strlenB` | faults when no NUL lies inside the block (the C reads past the object) |
17| `malloc` (through `ZALLOC`) | `allocB`, `allocW`, `allocK` per struct kind | the content is arbitrary integers and NULL pointers; success is decided by the oracle `Heap.allocOk`; faults if the heap is not `Fresh`; the size is a count of elements |
18| `free` | `freeB`, `freeW`, `freeK` | faults on a pointer that is not the start of a live block, on a freed block (double free), on a read-only block; NULL is a no-op |
19| string literal | `strLitRef` (the table `strTable`) | a static read-only array, the same pointer every time; a text outside the sources is a fault |
20| (test harness, not a C function) | `allocBytes`, `allocBytesRO` | put given bytes in a fresh block; take no allocation call |
21
22Not modelled: `calloc`, `realloc`, `printf` and the `gz` file calls other than through `GzFs` (the file system model of
23the translations, in `ZlibF/Zlib/gzfs.lean`), `errno`, `abs`, `strerror` (the translations write them themselves).
24-/
25
26namespace C
27
28/-! ## `memcpy`, `memmove`, `memset`, `strlen` on arrays -/
29
30/-- The value of an element of type `T` whose every byte is `c`: what `memset(p, c, n * sizeof(T))` stores in each
31element. -/
32class ByteRepeat (T : Type) where
33  ofByte : UInt8 → T
34
35instance : ByteRepeat UInt8 := ⟨id⟩
36instance : ByteRepeat UInt16 := ⟨fun c => (c.toUInt16 <<< 8) ||| c.toUInt16⟩
37instance : ByteRepeat UInt32 := ⟨fun c => let x := c.toUInt32; (x <<< 24) ||| (x <<< 16) ||| (x <<< 8) ||| x⟩
38instance : ByteRepeat UInt64 :=
39  ⟨fun c => let x := c.toUInt64; (x <<< 56) ||| (x <<< 48) ||| (x <<< 40) ||| (x <<< 32) ||| (x <<< 24) ||| (x <<< 16) ||| (x <<< 8) ||| x⟩
40instance : ByteRepeat Int8 := ⟨UInt8.toInt8⟩
41instance : ByteRepeat Int16 := ⟨fun c => ((c.toUInt16 <<< 8) ||| c.toUInt16).toInt16⟩
42instance : ByteRepeat Int32 := ⟨fun c => let x := c.toUInt32; ((x <<< 24) ||| (x <<< 16) ||| (x <<< 8) ||| x).toInt32⟩
43instance : ByteRepeat Int64 :=
44  ⟨fun c => let x := c.toUInt64; ((x <<< 56) ||| (x <<< 48) ||| (x <<< 40) ||| (x <<< 32) ||| (x <<< 24) ||| (x <<< 16) ||| (x <<< 8) ||| x).toInt64⟩
45
46section generic
47variable {R T : Type} [Space Heap R T]
48
49/-- `d` with `n` elements from `s` copied to it: element `doff + i` becomes element `soff + i` of `s`
50(read before any write, so it is also the result of `memmove`). -/
51def copyRange [Inhabited T] (d s : Array T) (doff soff n : Nat) : Array T :=
52  (List.range n).foldl (fun a i => a.set! (doff + i) s[soff + i]!) d
53
54/-- `d` with `n` elements from `off` on set to `v`. -/
55def fillRange (d : Array T) (off n : Nat) (v : T) : Array T :=
56  (List.range n).foldl (fun a i => a.set! (off + i) v) d
57
58/-- `memcpy(dst, src, n)` for `n` elements. Faults if either pointer is null (even for `n = 0`) or dangling, if a
59range leaves its array, if the destination is read-only and `n > 0`, or if the ranges overlap. -/
60def Ptr.memcpy [DecidableEq R] [Inhabited T] (dst src : Ptr R) (n : Nat) : M Unit := fun h =>
61  match dst, src with
62  | .ref rd od, .ref rs os =>
63    match Space.get h rd, Space.get h rs with
64    | some da, some sa =>
65      if Space.ro h rd && decide (n ≠ 0) then .error (.ub "memcpy into a read-only object")
66      else if od + n ≤ da.size ∧ os + n ≤ sa.size then
67        if rd ≠ rs ∨ od + n ≤ os ∨ os + n ≤ od then .ok ((), Space.modify h rd (fun da => copyRange da sa od os n))
68        else .error (.ub "memcpy with overlapping ranges")
69      else .error (.ub "memcpy outside its objects")
70    | _, _ => .error (.ub "memcpy through a dangling pointer")
71  | _, _ => .error (.ub "memcpy through a null pointer")
72
73/-- `memmove(dst, src, n)` for `n` elements: as `memcpy`, and the ranges may overlap (a read-only destination is a
74fault only when `n > 0`). -/
75def Ptr.memmove [DecidableEq R] [Inhabited T] (dst src : Ptr R) (n : Nat) : M Unit := fun h =>
76  match dst, src with
77  | .ref rd od, .ref rs os =>
78    match Space.get h rd, Space.get h rs with
79    | some da, some sa =>
80      if Space.ro h rd && decide (n ≠ 0) then .error (.ub "memmove into a read-only object")
81      else if od + n ≤ da.size ∧ os + n ≤ sa.size then .ok ((), Space.modify h rd (fun da => copyRange da sa od os n))
82      else .error (.ub "memmove outside its objects")
83    | _, _ => .error (.ub "memmove through a dangling pointer")
84  | _, _ => .error (.ub "memmove through a null pointer")
85
86/-- Set `n` elements to the value `v`. This is `memset(dst, c, n * sizeof(T))` only for a byte array, and for any
87element type when the value is zero (`zmemzero`). For a wider element type and another byte, use `Ptr.memset`, which
88stores the element whose every byte is `c`. Faults if `dst` is null or dangling, the range leaves the array, or the
89array is read-only and `n > 0`. -/
90def Ptr.fill (dst : Ptr R) (v : T) (n : Nat) : M Unit := fun h =>
91  match dst with
92  | .ref r off =>
93    match Space.get h r with
94    | none => .error (.ub "memset through a dangling pointer")
95    | some a =>
96      if Space.ro h r && decide (n ≠ 0) then .error (.ub "memset of a read-only object")
97      else if off + n ≤ a.size then .ok ((), Space.modify h r (fun a => fillRange a off n v))
98      else .error (.ub "memset outside its object")
99  | .null => .error (.ub "memset through a null pointer")
100
101/-- `memset(dst, c, n * sizeof(T))`: `n` elements, each with every byte equal to `c`. For a byte array it is `Ptr.fill`
102with `c`. The faults are those of `Ptr.fill`. -/
103def Ptr.memset [ByteRepeat T] (dst : Ptr R) (c : UInt8) (n : Nat) : M Unit :=
104  Ptr.fill dst (ByteRepeat.ofByte c) n
105
106/-! ### Rules -/
107
108theorem size_copyRange [Inhabited T] (d s : Array T) (doff soff n : Nat) :
109    (copyRange d s doff soff n).size = d.size := by
110  unfold copyRange
111  suffices ∀ (l : List Nat) (a : Array T), (l.foldl (fun a i => a.set! (doff + i) s[soff + i]!) a).size = a.size by
112    exact this _ _
113  intro l
114  induction l with
115  | nil => intro a; rfl
116  | cons i l ih => intro a; simp only [List.foldl]; rw [ih]; simp
117
118theorem size_fillRange (d : Array T) (off n : Nat) (v : T) : (fillRange d off n v).size = d.size := by
119  unfold fillRange
120  suffices ∀ (l : List Nat) (a : Array T), (l.foldl (fun a i => a.set! (off + i) v) a).size = a.size by
121    exact this _ _
122  intro l
123  induction l with
124  | nil => intro a; rfl
125  | cons i l ih => intro a; simp only [List.foldl]; rw [ih]; simp
126
127theorem Ptr.memcpy_ok [DecidableEq R] [Inhabited T] {h : Heap} {rd rs : R} {od os n : Nat} {da sa : Array T}
128    (hd : Space.get h rd = some da) (hs : Space.get h rs = some sa) (hro : Space.ro h rd = false)
129    (h1 : od + n ≤ da.size) (h2 : os + n ≤ sa.size) (hdis : rd ≠ rs ∨ od + n ≤ os ∨ os + n ≤ od) :
130    Ptr.memcpy (Ptr.ref rd od) (Ptr.ref rs os) n h = .ok ((), Space.put h rd (copyRange da sa od os n)) := by
131  simp [Ptr.memcpy, hd, hs, hro, h1, h2, hdis, Space.modify_eq _ _ _ _ hd]
132
133theorem Ptr.memmove_ok [DecidableEq R] [Inhabited T] {h : Heap} {rd rs : R} {od os n : Nat} {da sa : Array T}
134    (hd : Space.get h rd = some da) (hs : Space.get h rs = some sa) (hro : Space.ro h rd = false)
135    (h1 : od + n ≤ da.size) (h2 : os + n ≤ sa.size) :
136    Ptr.memmove (Ptr.ref rd od) (Ptr.ref rs os) n h = .ok ((), Space.put h rd (copyRange da sa od os n)) := by
137  simp [Ptr.memmove, hd, hs, hro, h1, h2, Space.modify_eq _ _ _ _ hd]
138
139theorem Ptr.fill_ok {h : Heap} {r : R} {a : Array T} {off n : Nat} {v : T}
140    (hg : Space.get h r = some a) (hro : Space.ro h r = false) (h1 : off + n ≤ a.size) :
141    Ptr.fill (Ptr.ref r off) v n h = .ok ((), Space.put h r (fillRange a off n v)) := by
142  simp [Ptr.fill, hg, hro, h1, Space.modify_eq _ _ _ _ hg]
143
144end generic
145
146/-- `memcpy(dst, src, n)` for byte pointers (`zmemcpy`). -/
147abbrev memcpyB (dst src : BPtr) (n : Nat) : M Unit := Ptr.memcpy dst src n
148/-- `memmove(dst, src, n)` for byte pointers. -/
149abbrev memmoveB (dst src : BPtr) (n : Nat) : M Unit := Ptr.memmove dst src n
150/-- `memset(dst, c, n)` for a byte pointer. -/
151abbrev memsetB (dst : BPtr) (c : UInt8) (n : Nat) : M Unit := Ptr.fill dst c n
152
153/-- `memcpy` between two different byte blocks, both writable-or-readable as needed: the destination block `nb` is
154writable, the ranges are inside the blocks. The result is the heap with the block `nb` replaced. -/
155theorem memcpyB_blk_ok {h : Heap} {nb no pb po n : Nat} {ob pbuf : Array UInt8}
156    (e1 : h.bytes nb = some ob) (e2 : h.bytes pb = some pbuf) (hro : h.ro nb = false) (hne : nb ≠ pb)
157    (h1 : no + n ≤ ob.size) (h2 : po + n ≤ pbuf.size) :
158    memcpyB (.ref (.blk nb) no) (.ref (.blk pb) po) n h =
159      .ok ((), { h with bytes := upd h.bytes nb (some (copyRange ob pbuf no po n)) }) :=
160  Ptr.memcpy_ok (rd := BRef.blk nb) (rs := BRef.blk pb) e1 e2 hro h1 h2 (Or.inl (fun hh => hne (BRef.blk.inj hh)))
161
162/-- The index of the first NUL byte of `a` at or after `i`. -/
163def strlenFrom (a : Array UInt8) (i : Nat) : Option Nat :=
164  if h : i < a.size then (if a[i] == 0 then some i else strlenFrom a (i + 1)) else none
165termination_by a.size - i
166
167/-- `strlen(p)`. Faults if `p` is null or dangling, or if no NUL byte follows inside the block (the C reads outside
168the object). -/
169def strlenB (p : BPtr) : M UInt64 := fun h =>
170  match p with
171  | .null => .error (.ub "strlen of a null pointer")
172  | .ref r off =>
173    match Space.get h r with
174    | none => .error (.ub "strlen through a dangling pointer")
175    | some a =>
176      match strlenFrom a off with
177      | some j => .ok (UInt64.ofNat (j - off), h)
178      | none => .error (.ub "strlen: no terminating NUL inside the object")
179
180theorem strlenFrom_eq (a : Array UInt8) (k : Nat) (hz : a[k]? = some 0) :
181    ∀ d i, i + d = k → (∀ j, i ≤ j → j < k → a[j]? ≠ some 0) → strlenFrom a i = some k := by
182  intro d
183  induction d with
184  | zero =>
185    intro i hi _
186    subst hi
187    have hlt : i < a.size := by
188      rcases Nat.lt_or_ge i a.size with hh | hh
189      · exact hh
190      · simp [Array.getElem?_eq_none hh] at hz
191    rw [strlenFrom]
192    simp [hlt]
193    have : a[i] = 0 := by simpa [hlt] using hz
194    simp [this]
195  | succ d ih =>
196    intro i hi hn
197    have hlt : i < a.size := by
198      rcases Nat.lt_or_ge i a.size with hh | hh
199      · exact hh
200      · simp [Array.getElem?_eq_none (by omega : a.size ≤ k)] at hz
201    have hne : a[i] ≠ 0 := by
202      intro h0
203      exact hn i (Nat.le_refl _) (by omega) (by simp [hlt, h0])
204    rw [strlenFrom]
205    simp only [hlt, dite_true]
206    have : (a[i] == 0) = false := by simpa using hne
207    simp only [this, Bool.false_eq_true, ite_false]
208    exact ih (i + 1) (by omega) (fun j hj hjk => hn j (by omega) hjk)
209
210theorem strlenB_ok {h : Heap} {r : BRef} {a : Array UInt8} {off k : Nat}
211    (hg : Space.get h r = some a) (hk : off ≤ k) (hz : a[k]? = some 0)
212    (hn : ∀ j, off ≤ j → j < k → a[j]? ≠ some 0) :
213    OkOn h (strlenB (Ptr.ref r off)) (UInt64.ofNat (k - off)) := by
214  unfold OkOn strlenB
215  simp [hg, strlenFrom_eq a k hz (k - off) off (by omega) hn]
216
217/-! ## `malloc`, `free` and string literals
218
219`malloc` and `free` through the default allocator, for byte blocks and for blocks of 16-bit words (the struct kinds
220have their own in `LibcTables.lean`). All kinds share one counter of block numbers: a new object takes `nextBlk`, which
221only grows, so a freed block number is never reused and a use after free is always detected. Whether the call succeeds
222is decided by the oracle `allocOk`; the content of the new memory is arbitrary (`garbage`), so a theorem holds for every
223content.
224
225**Block numbers.** `allocB`, `allocW`, `allocBytes`, `allocBytesRO` (and `allocK` for each struct kind) fault if the
226block number `nextBlk` is already in use (`Heap.slotUsed`). The fault is not a C behaviour: it says that the heap is not
227`Fresh`. A theorem about code that allocates carries `Fresh`; the rules (`allocB_some`, ...) take the hypothesis
228`h.slotUsed h.nextBlk = false`, which the tactic `fresh_slot` finds from `Fresh h` in the context. The names with the
229suffix `Chk` are aliases of the plain ones. -/
230
231
232/-- `malloc(n)` for `n` bytes: a fresh writable block with arbitrary content, or `NULL` when the failure oracle
233says so. -/
234def allocB (n : Nat) : M BPtr := fun h =>
235  if h.allocOk h.nalloc then
236    if h.slotUsed h.nextBlk then .error (.ub "allocation: the block number is in use (the heap is not Fresh)")
237    else
238      .ok (.ref (.blk h.nextBlk) 0,
239        { h with bytes := upd h.bytes h.nextBlk (some (Array.ofFn (n := n) fun i => Junk.of (h.garbage h.nalloc i.val))),
240                 ro := upd h.ro h.nextBlk false, nextBlk := h.nextBlk + 1, nalloc := h.nalloc + 1 })
241  else .ok (.null, { h with nalloc := h.nalloc + 1 })
242
243/-- For harnesses and tests, not a C operation: put the bytes `a` into a fresh writable block. It takes no allocation
244call of the oracle. -/
245def allocBytes (a : Array UInt8) : M BPtr := fun h =>
246  if h.slotUsed h.nextBlk then .error (.ub "allocation: the block number is in use (the heap is not Fresh)")
247  else
248    .ok (.ref (.blk h.nextBlk) 0,
249      { h with bytes := upd h.bytes h.nextBlk (some a), ro := upd h.ro h.nextBlk false, nextBlk := h.nextBlk + 1 })
250
251/-- For harnesses and tests, not a C operation: put the bytes `a` into a fresh read-only block (a string literal, a
252`const` buffer). -/
253def allocBytesRO (a : Array UInt8) : M BPtr := fun h =>
254  if h.slotUsed h.nextBlk then .error (.ub "allocation: the block number is in use (the heap is not Fresh)")
255  else
256    .ok (.ref (.blk h.nextBlk) 0,
257      { h with bytes := upd h.bytes h.nextBlk (some a), ro := upd h.ro h.nextBlk true, nextBlk := h.nextBlk + 1 })
258
259/-- `malloc(2 * n)` for `n` 16-bit words, as `allocB`. -/
260def allocW (n : Nat) : M WPtr := fun h =>
261  if h.allocOk h.nalloc then
262    if h.slotUsed h.nextBlk then .error (.ub "allocation: the block number is in use (the heap is not Fresh)")
263    else
264      .ok (.ref (.blk h.nextBlk) 0,
265        { h with words := upd h.words h.nextBlk (some (Array.ofFn (n := n) fun i => Junk.of (h.garbage h.nalloc i.val))),
266                 nextBlk := h.nextBlk + 1, nalloc := h.nalloc + 1 })
267  else .ok (.null, { h with nalloc := h.nalloc + 1 })
268
269/-- `free(p)` for a byte block: a no-op for `NULL`. Faults for a block that was freed already (double free), for a
270read-only block, and for a pointer that is not the start of an allocated block. -/
271def freeB (p : BPtr) : M Unit := fun h =>
272  match p with
273  | .null => .ok ((), h)
274  | .ref (.blk b) 0 =>
275    match h.bytes b with
276    | some _ => if h.ro b then .error (.ub "free of a read-only block") else .ok ((), { h with bytes := upd h.bytes b none })
277    | none => .error (.ub "free of a freed or invalid pointer")
278  | _ => .error (.ub "free of a pointer that is not the start of an allocated block")
279
280/-- `free(p)` for a block of 16-bit words, as `freeB`. -/
281def freeW (p : WPtr) : M Unit := fun h =>
282  match p with
283  | .null => .ok ((), h)
284  | .ref (.blk b) 0 =>
285    match h.words b with
286    | some _ => .ok ((), { h with words := upd h.words b none })
287    | none => .error (.ub "free of a freed or invalid pointer")
288  | _ => .error (.ub "free of a pointer that is not the start of an allocated block")
289
290/-- Alias of `allocB`. -/
291abbrev allocBChk := allocB
292/-- Alias of `allocW`. -/
293abbrev allocWChk := allocW
294/-- Alias of `allocBytes`. -/
295abbrev allocBytesChk := allocBytes
296/-- Alias of `allocBytesRO`. -/
297abbrev allocBytesROChk := allocBytesRO
298
299/-! ### String literals
300
301A literal is a static, read-only object that is never freed and never consumes an allocation (`BRef.str`, `strTable`
302in `Schema.lean`). `strLitRef s` is the pointer to the literal `s`: the same pointer every time, no change to the heap.
303The set of literals is closed (the string literals of the library sources); any other text is a fault. -/
304
305/-- The number of the literal `s` in `strTable`, if it is one. -/
306def strIndex (s : String) : Option Nat := strTable.findIdx? (fun t => t == s)
307
308/-- A string literal of the C sources: a pointer to its first byte. It reads the heap and does not change it; it
309faults if `s` is not a literal of the sources (`strTable`). -/
310def strLitRef (s : String) : M BPtr := fun h =>
311  match strIndex s with
312  | some i => .ok (.ref (.str i) 0, h)
313  | none => .error (.ub "string literal not in the table of literals of the sources")
314
315theorem strLitRef_of_index {h : Heap} {s : String} {i : Nat} (hi : strIndex s = some i) :
316    strLitRef s h = .ok (.ref (.str i) 0, h) := by
317  simp [strLitRef, hi]
318
319theorem strIndex_get {s : String} {i : Nat} (hi : strIndex s = some i) : strTable[i]? = some s := by
320  unfold strIndex at hi
321  rw [Array.findIdx?_eq_some_iff_getElem] at hi
322  obtain ⟨h1, h2, -⟩ := hi
323  rw [Array.getElem?_eq_getElem h1]
324  simpa using h2
325
326/-- Every text of `strTable` is found. -/
327theorem strIndex_of_mem {s : String} (hs : s ∈ strTable) : ∃ i, strIndex s = some i := by
328  cases hx : strIndex s with
329  | some i => exact ⟨i, rfl⟩
330  | none =>
331    exfalso
332    unfold strIndex at hx
333    rw [Array.findIdx?_eq_none_iff] at hx
334    have := hx s hs
335    simp at this
336
337/-- A literal of the sources: the pointer is the start of the read-only static array of its text and a NUL, and the
338heap is unchanged (no block is allocated). -/
339theorem strLitRef_of_mem {h : Heap} {s : String} (hs : s ∈ strTable) :
340    ∃ i, strLitRef s h = .ok (.ref (.str i) 0, h) ∧ strTable[i]? = some s := by
341  obtain ⟨i, hi⟩ := strIndex_of_mem hs
342  exact ⟨i, strLitRef_of_index hi, strIndex_get hi⟩
343
344/-- The bytes behind the pointer of a literal: the text and a NUL. -/
345theorem strLitRef_get {h : Heap} {s : String} {i : Nat} (hi : strIndex s = some i) :
346    Space.get h (BRef.str i) = some (strBytes s) := by
347  simp [strIndex_get hi]
348
349/-- A literal is read-only. -/
350theorem strLitRef_ro (h : Heap) (i : Nat) : Space.ro h (BRef.str i) = true := rfl
351
352/-! ### Rules -/
353
354theorem allocB_some {h : Heap} (n : Nat) (hok : h.allocOk h.nalloc = true)
355    (hs : h.slotUsed h.nextBlk = false := by fresh_slot) :
356    allocB n h = .ok (.ref (.blk h.nextBlk) 0,
357      { h with bytes := upd h.bytes h.nextBlk (some (Array.ofFn (n := n) fun i => Junk.of (h.garbage h.nalloc i.val))),
358               ro := upd h.ro h.nextBlk false, nextBlk := h.nextBlk + 1, nalloc := h.nalloc + 1 }) := by
359  simp [allocB, hok, hs]
360
361theorem allocBytesRO_ok {h : Heap} (a : Array UInt8) (hs : h.slotUsed h.nextBlk = false := by fresh_slot) :
362    allocBytesRO a h = .ok (.ref (.blk h.nextBlk) 0,
363      { h with bytes := upd h.bytes h.nextBlk (some a), ro := upd h.ro h.nextBlk true, nextBlk := h.nextBlk + 1 }) := by
364  simp [allocBytesRO, hs]
365
366theorem allocB_none {h : Heap} (n : Nat) (hok : ¬ h.allocOk h.nalloc = true) :
367    allocB n h = .ok (.null, { h with nalloc := h.nalloc + 1 }) := by
368  simp [allocB, hok]
369
370theorem allocW_some {h : Heap} (n : Nat) (hok : h.allocOk h.nalloc = true)
371    (hs : h.slotUsed h.nextBlk = false := by fresh_slot) :
372    allocW n h = .ok (.ref (.blk h.nextBlk) 0,
373      { h with words := upd h.words h.nextBlk (some (Array.ofFn (n := n) fun i => Junk.of (h.garbage h.nalloc i.val))),
374               nextBlk := h.nextBlk + 1, nalloc := h.nalloc + 1 }) := by
375  simp [allocW, hok, hs]
376
377theorem allocW_none {h : Heap} (n : Nat) (hok : ¬ h.allocOk h.nalloc = true) :
378    allocW n h = .ok (.null, { h with nalloc := h.nalloc + 1 }) := by
379  simp [allocW, hok]
380
381theorem freeB_null {h : Heap} : freeB .null h = .ok ((), h) := rfl
382
383theorem freeB_ok {h : Heap} {b : Nat} {a : Array UInt8} (hb : h.bytes b = some a) (hro : h.ro b = false) :
384    freeB (.ref (.blk b) 0) h = .ok ((), { h with bytes := upd h.bytes b none }) := by
385  simp [freeB, hb, hro]
386
387theorem freeB_freed {h : Heap} {b : Nat} (hb : h.bytes b = none) :
388    freeB (.ref (.blk b) 0) h = .error (.ub "free of a freed or invalid pointer") := by
389  simp [freeB, hb]
390
391theorem freeW_ok {h : Heap} {b : Nat} {a : Array UInt16} (hb : h.words b = some a) :
392    freeW (.ref (.blk b) 0) h = .ok ((), { h with words := upd h.words b none }) := by
393  simp [freeW, hb]
394
395/-! ### Faults and ownership
396
397A freed block is never reused, so a use after free and a double free are faults. `Owns` (see `Schema.lean`) says which
398blocks are live: the two lemmas below show how `allocB` and `freeB` change it, which is what a leak statement is built
399from. -/
400
401/-- A use after free is a fault: a read through a pointer into a block that was freed. -/
402theorem load_after_free {h : Heap} {b off : Nat} {a : Array UInt8} (hb : h.bytes b = some a) (hro : h.ro b = false) :
403    (freeB (.ref (.blk b) 0) >>= fun _ => load8 (.ref (.blk b) off)) h = .error (.ub "read through a dangling pointer") := by
404  rw [bind_app, freeB_ok hb hro]
405  simp [load8, Ptr.load]
406
407/-- A double free is a fault. -/
408theorem free_twice {h : Heap} {b : Nat} {a : Array UInt8} (hb : h.bytes b = some a) (hro : h.ro b = false) :
409    (freeB (.ref (.blk b) 0) >>= fun _ => freeB (.ref (.blk b) 0)) h = .error (.ub "free of a freed or invalid pointer") := by
410  rw [bind_app, freeB_ok hb hro]
411  simp [freeB]
412
413/-- A successful `allocB` adds exactly the new block to the live blocks. -/
414theorem allocB_owns {h : Heap} (n : Nat) {S : Nat → Prop} (hok : h.allocOk h.nalloc = true) (hO : h.Owns S)
415    (hs : h.slotUsed h.nextBlk = false := by fresh_slot) :
416    ∃ h', allocB n h = .ok (.ref (.blk h.nextBlk) 0, h') ∧ h'.Owns (fun b => S b ∨ b = h.nextBlk) := by
417  refine ⟨_, allocB_some n hok hs, ?_⟩
418  intro b
419  by_cases hb : b = h.nextBlk
420  · subst hb; simp [Heap.live]
421  · have := hO b
422    simp only [Heap.live] at this ⊢
423    simp [upd_ne _ _ _ _ hb, hb, this]
424
425/-- `free` of a byte block that no other table holds removes exactly that block from the live blocks. -/
426theorem freeB_owns {h : Heap} {b : Nat} {a : Array UInt8} {S : Nat → Prop} (hb : h.bytes b = some a)
427    (hro : h.ro b = false)
428    (hother : h.words b = none ∧ h.streams b = none ∧ h.gzheaders b = none ∧ h.dstates b = none ∧
429      h.istates b = none ∧ h.gzstates b = none)
430    (hO : h.Owns S) :
431    ∃ h', freeB (.ref (.blk b) 0) h = .ok ((), h') ∧ h'.Owns (fun x => S x ∧ x ≠ b) := by
432  refine ⟨_, freeB_ok hb hro, ?_⟩
433  intro x
434  by_cases hx : x = b
435  · subst hx
436    obtain ⟨e1, e2, e3, e4, e5, e6⟩ := hother
437    simp [Heap.live, e1, e2, e3, e4, e5, e6]
438  · have := hO x
439    simp only [Heap.live] at this ⊢
440    simp [upd_ne _ _ _ _ hx, hx, this]
441
442end C
ZLIB × LEAN

全局目录

当前位置Libc.lean