Skip to main content

amont_runtime/
json_read.rs

1//! A minimal JSON READER — the other half of [`crate::json`], for the one
2//! place amont must read JSON it did not write: npm's lockfiles, to tell
3//! whether the installed tree a tree gate would lint with is the locked one
4//! (ADR-0024). Dependency-free for the same reason `json.rs` is.
5//!
6//! Recursive descent over the RFC 8259 grammar. Numbers are kept as text:
7//! nothing here does arithmetic on them. Depth is bounded, so a hostile file
8//! cannot overflow the stack; a parse error is `None`, which every caller
9//! reads as "cannot tell", the fail-closed answer.
10
11#[derive(Debug, Clone, PartialEq)]
12pub enum Value {
13    Null,
14    Bool(bool),
15    Num(String),
16    Str(String),
17    Arr(Vec<Value>),
18    Obj(Vec<(String, Value)>),
19}
20
21const MAX_DEPTH: usize = 256;
22
23impl Value {
24    /// The member `key` of an object.
25    pub fn get(&self, key: &str) -> Option<&Value> {
26        match self {
27            Value::Obj(members) => members.iter().find(|(k, _)| k == key).map(|(_, v)| v),
28            _ => None,
29        }
30    }
31
32    pub fn as_str(&self) -> Option<&str> {
33        match self {
34            Value::Str(s) => Some(s),
35            _ => None,
36        }
37    }
38
39    pub fn is_true(&self) -> bool {
40        matches!(self, Value::Bool(true))
41    }
42
43    pub fn members(&self) -> &[(String, Value)] {
44        match self {
45            Value::Obj(m) => m,
46            _ => &[],
47        }
48    }
49
50    pub fn items(&self) -> &[Value] {
51        match self {
52            Value::Arr(a) => a,
53            _ => &[],
54        }
55    }
56}
57
58pub fn parse(text: &str) -> Option<Value> {
59    let mut p = Parser {
60        s: text.as_bytes(),
61        i: 0,
62    };
63    let v = p.value(0)?;
64    p.ws();
65    (p.i == p.s.len()).then_some(v)
66}
67
68struct Parser<'a> {
69    s: &'a [u8],
70    i: usize,
71}
72
73impl Parser<'_> {
74    fn ws(&mut self) {
75        while self.i < self.s.len() && matches!(self.s[self.i], b' ' | b'\t' | b'\n' | b'\r') {
76            self.i += 1;
77        }
78    }
79
80    fn eat(&mut self, lit: &[u8]) -> Option<()> {
81        if self.s.get(self.i..self.i + lit.len())? == lit {
82            self.i += lit.len();
83            Some(())
84        } else {
85            None
86        }
87    }
88
89    fn value(&mut self, depth: usize) -> Option<Value> {
90        if depth > MAX_DEPTH {
91            return None;
92        }
93        self.ws();
94        match *self.s.get(self.i)? {
95            b'{' => self.object(depth),
96            b'[' => self.array(depth),
97            b'"' => self.string().map(Value::Str),
98            b't' => self.eat(b"true").map(|_| Value::Bool(true)),
99            b'f' => self.eat(b"false").map(|_| Value::Bool(false)),
100            b'n' => self.eat(b"null").map(|_| Value::Null),
101            b'-' | b'0'..=b'9' => self.number(),
102            _ => None,
103        }
104    }
105
106    fn object(&mut self, depth: usize) -> Option<Value> {
107        self.i += 1;
108        let mut members = Vec::new();
109        self.ws();
110        if self.s.get(self.i) == Some(&b'}') {
111            self.i += 1;
112            return Some(Value::Obj(members));
113        }
114        loop {
115            self.ws();
116            let key = self.string()?;
117            self.ws();
118            self.eat(b":")?;
119            let v = self.value(depth + 1)?;
120            members.push((key, v));
121            self.ws();
122            match *self.s.get(self.i)? {
123                b',' => self.i += 1,
124                b'}' => {
125                    self.i += 1;
126                    return Some(Value::Obj(members));
127                }
128                _ => return None,
129            }
130        }
131    }
132
133    fn array(&mut self, depth: usize) -> Option<Value> {
134        self.i += 1;
135        let mut items = Vec::new();
136        self.ws();
137        if self.s.get(self.i) == Some(&b']') {
138            self.i += 1;
139            return Some(Value::Arr(items));
140        }
141        loop {
142            items.push(self.value(depth + 1)?);
143            self.ws();
144            match *self.s.get(self.i)? {
145                b',' => self.i += 1,
146                b']' => {
147                    self.i += 1;
148                    return Some(Value::Arr(items));
149                }
150                _ => return None,
151            }
152        }
153    }
154
155    fn number(&mut self) -> Option<Value> {
156        let start = self.i;
157        while self.i < self.s.len()
158            && matches!(
159                self.s[self.i],
160                b'-' | b'+' | b'.' | b'e' | b'E' | b'0'..=b'9'
161            )
162        {
163            self.i += 1;
164        }
165        let text = std::str::from_utf8(&self.s[start..self.i]).ok()?;
166        text.parse::<f64>().ok()?;
167        Some(Value::Num(text.to_string()))
168    }
169
170    fn string(&mut self) -> Option<String> {
171        if self.s.get(self.i) != Some(&b'"') {
172            return None;
173        }
174        self.i += 1;
175        let mut out: Vec<u8> = Vec::new();
176        loop {
177            let c = *self.s.get(self.i)?;
178            self.i += 1;
179            match c {
180                b'"' => return String::from_utf8(out).ok(),
181                b'\\' => {
182                    let e = *self.s.get(self.i)?;
183                    self.i += 1;
184                    match e {
185                        b'"' => out.push(b'"'),
186                        b'\\' => out.push(b'\\'),
187                        b'/' => out.push(b'/'),
188                        b'b' => out.push(8),
189                        b'f' => out.push(12),
190                        b'n' => out.push(b'\n'),
191                        b'r' => out.push(b'\r'),
192                        b't' => out.push(b'\t'),
193                        b'u' => {
194                            let hex = std::str::from_utf8(self.s.get(self.i..self.i + 4)?).ok()?;
195                            self.i += 4;
196                            let mut code = u32::from_str_radix(hex, 16).ok()?;
197                            // A surrogate pair spells one scalar value.
198                            if (0xD800..0xDC00).contains(&code) {
199                                self.eat(b"\\u")?;
200                                let lo =
201                                    std::str::from_utf8(self.s.get(self.i..self.i + 4)?).ok()?;
202                                self.i += 4;
203                                let lo = u32::from_str_radix(lo, 16).ok()?;
204                                code =
205                                    0x10000 + ((code - 0xD800) << 10) + (lo.checked_sub(0xDC00)?);
206                            }
207                            let ch = char::from_u32(code)?;
208                            let mut buf = [0u8; 4];
209                            out.extend_from_slice(ch.encode_utf8(&mut buf).as_bytes());
210                        }
211                        _ => return None,
212                    }
213                }
214                c if c < 0x20 => return None,
215                c => out.push(c),
216            }
217        }
218    }
219}
220
221#[cfg(test)]
222mod tests {
223    use super::*;
224
225    #[test]
226    fn json_read_parses_a_lockfile_shape() {
227        let v = parse(
228            r#"{"name":"x","lockfileVersion":3,"packages":{"":{"name":"x"},
229               "node_modules/a":{"version":"1.0.0","integrity":"sha512-AA==","dev":true},
230               "node_modules/@esbuild/darwin-arm64":{"version":"0.2.0","optional":true,"os":["darwin"],"cpu":["arm64"]}}}"#,
231        )
232        .expect("parses");
233        let pk = v.get("packages").unwrap();
234        assert_eq!(pk.members().len(), 3);
235        let a = pk.get("node_modules/a").unwrap();
236        assert_eq!(a.get("version").and_then(Value::as_str), Some("1.0.0"));
237        assert!(a.get("dev").unwrap().is_true());
238        let e = pk.get("node_modules/@esbuild/darwin-arm64").unwrap();
239        assert_eq!(e.get("os").unwrap().items()[0].as_str(), Some("darwin"));
240    }
241
242    #[test]
243    fn json_read_escapes_and_unicode() {
244        let v = parse(r#"["a\"b\\c\n", "é😀", -1.5e3, null, false]"#).unwrap();
245        let items = v.items();
246        assert_eq!(items[0].as_str(), Some("a\"b\\c\n"));
247        assert_eq!(items[1].as_str(), Some("é😀"));
248        assert_eq!(items[2], Value::Num("-1.5e3".into()));
249        assert_eq!(items[3], Value::Null);
250    }
251
252    #[test]
253    fn json_read_refuses_garbage_and_depth_bombs() {
254        assert!(parse("{\"a\":}").is_none());
255        assert!(parse("[1,2] trailing").is_none());
256        assert!(parse(&"[".repeat(10_000)).is_none());
257    }
258}