Skip to main content

vstd/
string.rs

1#![feature(rustc_attrs)]
2#![allow(unused_imports)]
3
4#[cfg(all(feature = "alloc", not(verus_verify_core)))]
5use alloc::str::Chars;
6#[cfg(all(feature = "alloc", not(verus_verify_core)))]
7use alloc::string::{self, String, ToString};
8
9use super::prelude::*;
10use super::seq::Seq;
11use super::slice::*;
12#[cfg(verus_keep_ghost)]
13#[cfg(all(feature = "alloc", not(verus_verify_core)))]
14use super::std_specs::iter::IteratorSpec;
15use super::utf8::*;
16use super::view::*;
17
18verus! {
19
20broadcast use {super::seq::group_seq_lemmas, super::slice::group_slice_axioms};
21
22#[cfg(not(verus_verify_core))]
23impl View for str {
24    type V = Seq<char>;
25
26    uninterp spec fn view(&self) -> Seq<char>;
27}
28
29#[cfg(not(verus_verify_core))]
30impl DeepView for str {
31    type V = Seq<char>;
32
33    open spec fn deep_view(&self) -> Seq<char> {
34        self.view()
35    }
36}
37
38#[cfg(not(verus_verify_core))]
39pub trait StringSliceAdditionalSpecFns {
40    spec fn spec_bytes(&self) -> Seq<u8>;
41}
42
43#[cfg(not(verus_verify_core))]
44impl StringSliceAdditionalSpecFns for str {
45    open spec fn spec_bytes(&self) -> Seq<u8> {
46        encode_utf8(self@)
47    }
48}
49
50#[cfg(not(verus_verify_core))]
51pub open spec fn is_ascii(s: &str) -> bool {
52    is_ascii_chars(s@)
53}
54
55#[cfg(not(verus_verify_core))]
56pub broadcast proof fn is_ascii_spec_bytes(s: &str)
57    ensures
58        #[trigger] is_ascii(s) ==> #[trigger] s.spec_bytes() =~= Seq::new(
59            s@.len(),
60            |i| s@.index(i) as u8,
61        ),
62{
63    if (is_ascii(s)) {
64        is_ascii_chars_encode_utf8(s@);
65    }
66}
67
68#[cfg(not(verus_verify_core))]
69pub broadcast proof fn is_ascii_concat(s1: &str, s2: &str, s3: &str)
70    requires
71        s1@ =~= s2@ + s3@,
72    ensures
73        #![trigger s2@ + s3@, is_ascii(s1), is_ascii(s2), is_ascii(s3)]
74        is_ascii(s1) <==> is_ascii(s2) && is_ascii(s3),
75{
76    broadcast use is_ascii_chars_concat;
77
78    if (is_ascii(s1)) {
79        is_ascii_chars_concat(s1@, s2@, s3@);
80    }
81}
82
83#[cfg(not(verus_verify_core))]
84#[verifier::when_used_as_spec(is_ascii)]
85pub assume_specification[ str::is_ascii ](s: &str) -> (b: bool)
86    ensures
87        b == is_ascii(s),
88;
89
90#[cfg(all(feature = "alloc", not(verus_verify_core)))]
91use crate::alloc::borrow::ToOwned;
92
93#[cfg(all(feature = "alloc", not(verus_verify_core)))]
94pub assume_specification[ str::to_owned ](s: &str) -> (res: String)
95    ensures
96        s@ == res@,
97;
98
99#[cfg(not(verus_verify_core))]
100pub assume_specification[ str::as_bytes ](s: &str) -> (b: &[u8])
101    ensures
102        b@ == s.spec_bytes(),
103;
104
105#[cfg(not(verus_verify_core))]
106#[verifier::allow_in_spec]
107pub assume_specification[ str::len ](s: &str) -> usize
108    returns
109        s.spec_bytes().len() as usize,
110;
111
112#[cfg(not(verus_verify_core))]
113#[verifier::allow_in_spec]
114pub assume_specification[ str::is_empty ](s: &str) -> bool
115    returns
116        s@.len() == 0,
117;
118
119#[cfg(not(verus_verify_core))]
120#[verifier::allow_in_spec]
121pub assume_specification[ str::is_char_boundary ](s: &str, index: usize) -> bool
122    returns
123        is_char_boundary(s.spec_bytes(), index as int),
124;
125
126#[cfg(not(verus_verify_core))]
127pub assume_specification[ str::split_at ](s: &str, mid: usize) -> (res: (&str, &str))
128    requires
129        is_char_boundary(s.spec_bytes(), mid as int),
130    ensures
131        res.0.spec_bytes() =~= s.spec_bytes().subrange(0, mid as int),
132        res.1.spec_bytes() =~= s.spec_bytes().subrange(mid as int, s.spec_bytes().len() as int),
133;
134
135#[cfg(not(verus_verify_core))]
136pub assume_specification[ str::from_utf8_unchecked ](v: &[u8]) -> (res: &str)
137    requires
138        valid_utf8(v@),
139    ensures
140        res.spec_bytes() =~= v@,
141;
142
143#[cfg(all(feature = "alloc", not(verus_verify_core)))]
144pub uninterp spec fn to_string_from_display_ensures<T: core::fmt::Display + ?Sized>(
145    t: &T,
146    s: String,
147) -> bool;
148
149#[cfg(all(feature = "alloc", not(verus_verify_core)))]
150pub broadcast proof fn to_string_from_display_ensures_for_str(t: &str, res: String)
151    ensures
152        #[trigger] to_string_from_display_ensures::<str>(t, res) <==> (t@ == res@),
153{
154    admit();
155}
156
157#[cfg(all(feature = "alloc", not(verus_verify_core)))]
158pub assume_specification<T: core::fmt::Display + ?Sized>[ <T as ToString>::to_string ](
159    t: &T,
160) -> (res: String)
161    ensures
162        to_string_from_display_ensures::<T>(t, res),
163;
164
165#[cfg(not(verus_verify_core))]
166#[verifier::external]
167pub trait StrSliceExecFns {
168    fn unicode_len(&self) -> usize;
169
170    fn get_char(&self, i: usize) -> char;
171
172    fn substring_ascii<'a>(&'a self, from: usize, to: usize) -> &'a str;
173
174    fn substring_char<'a>(&'a self, from: usize, to: usize) -> &'a str;
175
176    fn get_ascii(&self, i: usize) -> u8;
177
178    #[cfg(feature = "alloc")]
179    fn as_bytes_vec(&self) -> alloc::vec::Vec<u8>;
180}
181
182#[cfg(not(verus_verify_core))]
183impl StrSliceExecFns for str {
184    /// The len() function in rust returns the byte length.
185    /// It is more useful to talk about the length of characters and therefore this function was added.
186    /// Please note that this function counts the unicode variation selectors as characters.
187    /// Warning: O(n)
188    #[verifier::external_body]
189    fn unicode_len(&self) -> (l: usize)
190        ensures
191            l as nat == self@.len(),
192    {
193        self.chars().count()
194    }
195
196    /// Warning: O(n) not O(1) due to unicode decoding needed
197    #[verifier::external_body]
198    fn get_char(&self, i: usize) -> (c: char)
199        requires
200            i < self@.len(),
201        ensures
202            self@.index(i as int) == c,
203    {
204        self.chars().nth(i).unwrap()
205    }
206
207    #[verifier::external_body]
208    fn substring_ascii<'a>(&'a self, from: usize, to: usize) -> (ret: &'a str)
209        requires
210            self.is_ascii(),
211            from <= to <= self@.len(),
212        ensures
213            ret@ == self@.subrange(from as int, to as int),
214            ret.is_ascii(),
215    {
216        // Range::index panics if from > to or from > self@.len()
217        &self[from..to]
218    }
219
220    #[verifier::external_body]
221    fn substring_char<'a>(&'a self, from: usize, to: usize) -> (ret: &'a str)
222        requires
223            from <= to <= self@.len(),
224        ensures
225            ret@ == self@.subrange(from as int, to as int),
226    {
227        let mut char_pos = 0;
228        let mut byte_start = None;
229        let mut byte_end = None;
230        let mut byte_pos = 0;
231        let mut it = self.chars();
232        loop {
233            if char_pos == from {
234                byte_start = Some(byte_pos);
235            }
236            if char_pos == to {
237                byte_end = Some(byte_pos);
238                break;
239            }
240            if let Some(c) = it.next() {
241                char_pos += 1;
242                byte_pos += c.len_utf8();
243            } else {
244                break;
245            }
246        }
247        let byte_start = byte_start.unwrap();
248        let byte_end = byte_end.unwrap();
249        // Range::index panics if from > to or from > self@.len()
250        &self[byte_start..byte_end]
251    }
252
253    fn get_ascii(&self, i: usize) -> (b: u8)
254        requires
255            self.is_ascii(),
256            i < self@.len(),
257        ensures
258            self@.index(i as int) as u8 == b,
259    {
260        broadcast use is_ascii_spec_bytes;
261        // panics if i is not a valid index
262
263        self.as_bytes()[i]
264    }
265
266    #[cfg(feature = "alloc")]
267    fn as_bytes_vec(&self) -> (ret: alloc::vec::Vec<u8>)
268        ensures
269            ret@ == self.spec_bytes(),
270    {
271        slice_to_vec(self.as_bytes())
272    }
273}
274
275#[cfg(not(verus_verify_core))]
276pub broadcast axiom fn axiom_str_literal_len<'a>(s: &'a str)
277    ensures
278        #[trigger] s@.len() == strslice_len(s),
279;
280
281#[cfg(not(verus_verify_core))]
282pub broadcast axiom fn axiom_str_literal_get_char<'a>(s: &'a str, i: int)
283    ensures
284        #[trigger] s@.index(i) == strslice_get_char(s, i),
285;
286
287#[cfg(all(not(feature = "alloc"), not(verus_verify_core)))]
288pub broadcast group group_string_axioms {
289    axiom_str_literal_len,
290    axiom_str_literal_get_char,
291    is_ascii_spec_bytes,
292    is_ascii_concat,
293}
294
295#[cfg(all(feature = "alloc", not(verus_verify_core)))]
296pub broadcast group group_string_axioms {
297    axiom_str_literal_len,
298    axiom_str_literal_get_char,
299    to_string_from_display_ensures_for_str,
300    is_ascii_spec_bytes,
301    is_ascii_concat,
302}
303
304#[cfg(all(feature = "alloc", not(verus_verify_core)))]
305impl View for String {
306    type V = Seq<char>;
307
308    uninterp spec fn view(&self) -> Seq<char>;
309}
310
311#[cfg(all(feature = "alloc", not(verus_verify_core)))]
312impl DeepView for String {
313    type V = Seq<char>;
314
315    open spec fn deep_view(&self) -> Seq<char> {
316        self.view()
317    }
318}
319
320#[cfg(all(feature = "alloc", not(verus_verify_core)))]
321#[verifier::external_type_specification]
322#[verifier::external_body]
323pub struct ExString(String);
324
325#[cfg(all(feature = "alloc", not(verus_verify_core)))]
326pub open spec fn string_is_ascii(s: &String) -> bool {
327    is_ascii_chars(s@)
328}
329
330#[cfg(all(feature = "alloc", not(verus_verify_core)))]
331pub assume_specification<'a>[ String::as_str ](s: &'a String) -> (res: &'a str)
332    ensures
333        res@ == s@,
334;
335
336// same as above
337#[cfg(all(feature = "alloc", not(verus_verify_core)))]
338pub assume_specification<'a>[ <String as core::ops::Deref>::deref ](s: &'a String) -> (res: &'a str)
339    ensures
340        res@ == s@,
341;
342
343#[cfg(all(feature = "alloc", not(verus_verify_core)))]
344pub assume_specification[ <String as Clone>::clone ](s: &String) -> (res: String)
345    ensures
346        res == s,
347;
348
349#[cfg(all(feature = "alloc", not(verus_verify_core)))]
350pub assume_specification[ <String as PartialEq>::eq ](s: &String, other: &String) -> (res: bool)
351    ensures
352        res == (s@ == other@),
353;
354
355#[cfg(all(feature = "alloc", not(verus_verify_core)))]
356pub assume_specification[ String::new ]() -> (res: String)
357    ensures
358        res@ == Seq::<char>::empty(),
359;
360
361#[cfg(all(feature = "alloc", not(verus_verify_core)))]
362pub assume_specification[ String::push ](s: &mut String, c: char)
363    ensures
364        final(s)@ == old(s)@.push(c),
365;
366
367#[cfg(all(feature = "alloc", not(verus_verify_core)))]
368pub assume_specification[ String::pop ](s: &mut String) -> (res: Option<char>)
369    ensures
370        old(s)@.len() == 0 ==> res is None && final(s)@ == old(s)@,
371        old(s)@.len() > 0 ==> res == Some(old(s)@.last()) && final(s)@ == old(s)@.drop_last(),
372;
373
374#[cfg(all(feature = "alloc", not(verus_verify_core)))]
375pub assume_specification[ String::push_str ](s: &mut String, other: &str)
376    ensures
377        final(s)@ == old(s)@ + other@,
378;
379
380#[cfg(all(feature = "alloc", not(verus_verify_core)))]
381pub assume_specification[ String::is_empty ](s: &String) -> (res: bool)
382    ensures
383        res == (s@.len() == 0),
384;
385
386#[cfg(all(feature = "alloc", not(verus_verify_core)))]
387pub assume_specification[ String::clear ](s: &mut String)
388    ensures
389        final(s)@ == Seq::<char>::empty(),
390;
391
392#[cfg(all(feature = "alloc", not(verus_verify_core)))]
393pub assume_specification[ <String as core::default::Default>::default ]() -> (r: String)
394    ensures
395        r@ == Seq::<char>::empty(),
396;
397
398#[cfg(all(feature = "alloc", not(verus_verify_core)))]
399pub trait StringExecFnsIsAscii: Sized {
400    fn is_ascii(&self) -> bool;
401}
402
403#[cfg(all(feature = "alloc", not(verus_verify_core)))]
404impl StringExecFnsIsAscii for String {
405    #[inline(always)]
406    #[verifier::when_used_as_spec(string_is_ascii)]
407    fn is_ascii(&self) -> (ret: bool)
408        ensures
409            ret == string_is_ascii(self),
410    {
411        self.as_str().is_ascii()
412    }
413}
414
415#[cfg(all(feature = "alloc", not(verus_verify_core)))]
416#[verifier::external]
417pub trait StringExecFns: Sized {
418    fn from_str<'a>(s: &'a str) -> String;
419
420    fn append<'a, 'b>(&'a mut self, other: &'b str);
421
422    fn concat<'b>(self, other: &'b str) -> String;
423}
424
425#[cfg(all(feature = "alloc", not(verus_verify_core)))]
426impl StringExecFns for String {
427    #[verifier::external_body]
428    fn from_str<'a>(s: &'a str) -> (ret: String)
429        ensures
430            s@ == ret@,
431    {
432        s.to_string()
433    }
434
435    #[verifier::external_body]
436    fn append<'a, 'b>(&'a mut self, other: &'b str)
437        ensures
438            final(self)@ == old(self)@ + other@,
439    {
440        *self += other;
441    }
442
443    #[verifier::external_body]
444    fn concat<'b>(self, other: &'b str) -> (ret: String)
445        ensures
446            ret@ == self@ + other@,
447    {
448        self + other
449    }
450}
451
452// The `chars` method of a `str` returns an iterator of type `Chars`,
453// so we specify that type here.
454#[verifier::external_type_specification]
455#[verifier::external_body]
456#[cfg(all(feature = "alloc", not(verus_verify_core)))]
457pub struct ExChars<'a>(Chars<'a>);
458
459// To allow reasoning about the "contents" of the string iterator, without using
460// a prophecy, we need a function that gives us the underlying sequence of the original string.
461#[cfg(feature = "alloc")]
462pub uninterp spec fn into_iter_elts<'a>(i: Chars<'a>) -> Seq<char>;
463
464#[cfg(feature = "alloc")]
465pub assume_specification[ str::chars ](s: &str) -> (iter: Chars<'_>)
466    ensures
467        IteratorSpec::remaining(&iter) == s@,
468        IteratorSpec::decrease(&iter) is Some,
469;
470
471#[cfg(verus_keep_ghost)]
472#[cfg(feature = "alloc")]
473impl<'a> super::std_specs::iter::IteratorSpecImpl for Chars<'a> {
474    open spec fn obeys_prophetic_iter_laws(&self) -> bool {
475        true
476    }
477
478    uninterp spec fn remaining(&self) -> Seq<Self::Item>;
479
480    uninterp spec fn will_return_none(&self) -> bool;
481
482    uninterp spec fn decrease(&self) -> Option<nat>;
483
484    open spec fn peek(&self, index: int) -> Option<Self::Item> {
485        if 0 <= index < into_iter_elts(*self).len() {
486            Some(into_iter_elts(*self)[index])
487        } else {
488            None
489        }
490    }
491}
492
493pub use super::view::View;
494
495} // verus!