Skip to main content

snarkvm_console_types_string/
parse.rs

1// Copyright (c) 2019-2026 Provable Inc.
2// This file is part of the snarkVM library.
3
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at:
7
8// http://www.apache.org/licenses/LICENSE-2.0
9
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16use super::*;
17
18impl<E: Environment> Parser for StringType<E> {
19    /// Parses a string into a string type.
20    #[inline]
21    fn parse(string: &str) -> ParserResult<Self> {
22        // Parse the starting and ending quote '"' keyword from the string.
23        let (string, value) = string_parser::parse_string(string)?;
24
25        // Return an error if the literal exceeds the maximum length. Note: this must be checked here
26        // rather than left to `StringType::new`, which halts instead of failing; a parser is reached
27        // from untrusted input, such as a program deserialized from its human-readable form.
28        if value.len() > E::MAX_STRING_BYTES as usize {
29            return Err(Err::Error(make_error(string, ErrorKind::TooLarge)));
30        }
31
32        Ok((string, StringType::new(&value)))
33    }
34}
35
36impl<E: Environment> FromStr for StringType<E> {
37    type Err = Error;
38
39    /// Parses a string into a string type.
40    #[inline]
41    fn from_str(string: &str) -> Result<Self> {
42        match Self::parse(string) {
43            Ok((remainder, object)) => {
44                // Ensure the remainder is empty.
45                ensure!(remainder.is_empty(), "Failed to parse string. Found invalid character in: \"{remainder}\"");
46                // Return the object.
47                Ok(object)
48            }
49            Err(error) => bail!("Failed to parse string. {error}"),
50        }
51    }
52}
53
54impl<E: Environment> Debug for StringType<E> {
55    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
56        Display::fmt(self, f)
57    }
58}
59
60impl<E: Environment> Display for StringType<E> {
61    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
62        write!(f, "\"{}\"", self.string)
63    }
64}
65
66#[cfg(test)]
67mod tests {
68    use super::*;
69    use snarkvm_console_network_environment::Console;
70
71    type CurrentEnvironment = Console;
72
73    const ITERATIONS: u32 = 100;
74
75    #[test]
76    fn test_display() -> Result<()> {
77        // Ensure type and empty value fails.
78        assert!(StringType::<CurrentEnvironment>::parse(StringType::<CurrentEnvironment>::type_name()).is_err());
79        assert!(StringType::<CurrentEnvironment>::parse("").is_err());
80
81        // Ensure empty string succeeds.
82        assert!(StringType::<CurrentEnvironment>::parse("\"\"").is_ok());
83
84        let rng = &mut TestRng::default();
85
86        for _ in 0..ITERATIONS {
87            // Sample a random string. Take 1/4th to ensure we fit for all code points.
88            let expected = rng.next_string(CurrentEnvironment::MAX_STRING_BYTES / 4, false);
89            let expected_num_bytes = expected.len();
90            assert!(expected_num_bytes <= CurrentEnvironment::MAX_STRING_BYTES as usize);
91
92            let candidate = StringType::<CurrentEnvironment>::new(&expected);
93            assert_eq!(format!("\"{expected}\""), format!("{candidate}"));
94
95            let candidate_recovered = StringType::<CurrentEnvironment>::from_str(&format!("{candidate}")).unwrap();
96            assert_eq!(candidate, candidate_recovered);
97        }
98        Ok(())
99    }
100
101    #[test]
102    fn test_parse_unsupported_code_points() -> Result<()> {
103        const UNSUPPORTED_CODE_POINTS: [&str; 9] = [
104            "\u{202a}", "\u{202b}", "\u{202c}", "\u{202d}", "\u{202e}", "\u{2066}", "\u{2067}", "\u{2068}", "\u{2069}",
105        ];
106
107        // Ensure that the invalid code point is not allowed in the string.
108        for unsupported_code_point in UNSUPPORTED_CODE_POINTS {
109            assert!(StringType::<CurrentEnvironment>::parse(unsupported_code_point).is_err());
110        }
111
112        Ok(())
113    }
114
115    #[test]
116    fn test_parse_oversized_string_fails_without_halting() {
117        let max_bytes = CurrentEnvironment::MAX_STRING_BYTES as usize;
118
119        // A literal of the maximum length is accepted.
120        let at_capacity = format!("\"{}\"", "a".repeat(max_bytes));
121        assert!(StringType::<CurrentEnvironment>::parse(&at_capacity).is_ok());
122
123        // A literal that exceeds it is rejected, rather than halting. Note: `StringType::new` halts on
124        // an oversized string, so the parser has to reject it beforehand; it is reachable from
125        // untrusted input, such as a program deserialized from its human-readable form.
126        for excess in 1..=4 {
127            let over_capacity = format!("\"{}\"", "a".repeat(max_bytes + excess));
128            assert!(StringType::<CurrentEnvironment>::parse(&over_capacity).is_err());
129            assert!(StringType::<CurrentEnvironment>::from_str(&over_capacity).is_err());
130        }
131    }
132}