/src/swift-protobuf/Sources/SwiftProtobuf/JSONScanner.swift
Line | Count | Source |
1 | | // Sources/SwiftProtobuf/JSONScanner.swift - JSON format decoding |
2 | | // |
3 | | // Copyright (c) 2014 - 2019 Apple Inc. and the project authors |
4 | | // Licensed under Apache License v2.0 with Runtime Library Exception |
5 | | // |
6 | | // See LICENSE.txt for license information: |
7 | | // https://github.com/apple/swift-protobuf/blob/main/LICENSE.txt |
8 | | // |
9 | | // ----------------------------------------------------------------------------- |
10 | | /// |
11 | | /// JSON format decoding engine. |
12 | | /// |
13 | | // ----------------------------------------------------------------------------- |
14 | | |
15 | | #if canImport(FoundationEssentials) |
16 | | import FoundationEssentials |
17 | | #else |
18 | | import Foundation |
19 | | #endif |
20 | | |
21 | | private let asciiBell = UInt8(7) |
22 | | private let asciiBackspace = UInt8(8) |
23 | | private let asciiTab = UInt8(9) |
24 | | private let asciiNewLine = UInt8(10) |
25 | | private let asciiVerticalTab = UInt8(11) |
26 | | private let asciiFormFeed = UInt8(12) |
27 | | private let asciiCarriageReturn = UInt8(13) |
28 | | private let asciiZero = UInt8(ascii: "0") |
29 | | private let asciiOne = UInt8(ascii: "1") |
30 | | private let asciiSeven = UInt8(ascii: "7") |
31 | | private let asciiNine = UInt8(ascii: "9") |
32 | | private let asciiColon = UInt8(ascii: ":") |
33 | | private let asciiPeriod = UInt8(ascii: ".") |
34 | | private let asciiPlus = UInt8(ascii: "+") |
35 | | private let asciiComma = UInt8(ascii: ",") |
36 | | private let asciiSemicolon = UInt8(ascii: ";") |
37 | | private let asciiDoubleQuote = UInt8(ascii: "\"") |
38 | | private let asciiSingleQuote = UInt8(ascii: "\'") |
39 | | private let asciiBackslash = UInt8(ascii: "\\") |
40 | | private let asciiForwardSlash = UInt8(ascii: "/") |
41 | | private let asciiHash = UInt8(ascii: "#") |
42 | | private let asciiEqualSign = UInt8(ascii: "=") |
43 | | private let asciiUnderscore = UInt8(ascii: "_") |
44 | | private let asciiQuestionMark = UInt8(ascii: "?") |
45 | | private let asciiSpace = UInt8(ascii: " ") |
46 | | private let asciiOpenSquareBracket = UInt8(ascii: "[") |
47 | | private let asciiCloseSquareBracket = UInt8(ascii: "]") |
48 | | private let asciiOpenCurlyBracket = UInt8(ascii: "{") |
49 | | private let asciiCloseCurlyBracket = UInt8(ascii: "}") |
50 | | private let asciiOpenAngleBracket = UInt8(ascii: "<") |
51 | | private let asciiCloseAngleBracket = UInt8(ascii: ">") |
52 | | private let asciiMinus = UInt8(ascii: "-") |
53 | | private let asciiLowerA = UInt8(ascii: "a") |
54 | | private let asciiUpperA = UInt8(ascii: "A") |
55 | | private let asciiLowerB = UInt8(ascii: "b") |
56 | | private let asciiLowerE = UInt8(ascii: "e") |
57 | | private let asciiUpperE = UInt8(ascii: "E") |
58 | | private let asciiLowerF = UInt8(ascii: "f") |
59 | | private let asciiUpperI = UInt8(ascii: "I") |
60 | | private let asciiLowerL = UInt8(ascii: "l") |
61 | | private let asciiLowerN = UInt8(ascii: "n") |
62 | | private let asciiUpperN = UInt8(ascii: "N") |
63 | | private let asciiLowerR = UInt8(ascii: "r") |
64 | | private let asciiLowerS = UInt8(ascii: "s") |
65 | | private let asciiLowerT = UInt8(ascii: "t") |
66 | | private let asciiLowerU = UInt8(ascii: "u") |
67 | | private let asciiLowerZ = UInt8(ascii: "z") |
68 | | private let asciiUpperZ = UInt8(ascii: "Z") |
69 | | |
70 | 17.3k | private func fromHexDigit(_ c: UnicodeScalar) -> UInt32? { |
71 | 17.3k | let n = c.value |
72 | 17.3k | if n >= 48 && n <= 57 { |
73 | 3.72k | return UInt32(n - 48) |
74 | 13.5k | } |
75 | 13.5k | switch n { |
76 | 13.5k | case 65, 97: return 10 |
77 | 13.5k | case 66, 98: return 11 |
78 | 13.5k | case 67, 99: return 12 |
79 | 13.5k | case 68, 100: return 13 |
80 | 13.5k | case 69, 101: return 14 |
81 | 13.5k | case 70, 102: return 15 |
82 | 13.5k | default: |
83 | 79 | return nil |
84 | 13.5k | } |
85 | 13.5k | } |
86 | | |
87 | | // Decode both the RFC 4648 section 4 Base 64 encoding and the RFC |
88 | | // 4648 section 5 Base 64 variant. The section 5 variant is also |
89 | | // known as "base64url" or the "URL-safe alphabet". |
90 | | // Note that both "-" and "+" decode to 62 and "/" and "_" both |
91 | | // decode as 63. |
92 | | // swift-format-ignore: NoBlockComments |
93 | | let base64Values: [Int] = [ |
94 | | /* 0x00 */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, |
95 | | /* 0x10 */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, |
96 | | /* 0x20 */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, 62, -1, 63, |
97 | | /* 0x30 */ 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, |
98 | | /* 0x40 */ -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, |
99 | | /* 0x50 */ 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, 63, |
100 | | /* 0x60 */ -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, |
101 | | /* 0x70 */ 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, |
102 | | /* 0x80 */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, |
103 | | /* 0x90 */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, |
104 | | /* 0xa0 */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, |
105 | | /* 0xb0 */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, |
106 | | /* 0xc0 */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, |
107 | | /* 0xd0 */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, |
108 | | /* 0xe0 */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, |
109 | | /* 0xf0 */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, |
110 | | ] |
111 | | |
112 | | /// Returns a `Data` value containing bytes equivalent to the given |
113 | | /// Base64-encoded string, or nil if the conversion fails. |
114 | | /// |
115 | | /// Notes on Google's implementation (Base64Unescape() in strutil.cc): |
116 | | /// * Google's C++ implementation accepts arbitrary whitespace |
117 | | /// mixed in with the base-64 characters |
118 | | /// * Google's C++ implementation ignores missing '=' characters |
119 | | /// but if present, there must be the exact correct number of them. |
120 | | /// * The conformance test requires us to accept both standard RFC4648 |
121 | | /// Base 64 encoding and the "URL and Filename Safe Alphabet" variant. |
122 | | /// |
123 | | private func parseBytes( |
124 | | source: UnsafeRawBufferPointer, |
125 | | index: inout UnsafeRawBufferPointer.Index, |
126 | | end: UnsafeRawBufferPointer.Index |
127 | 8.25k | ) throws -> Data { |
128 | 8.25k | let c = source[index] |
129 | 8.25k | if c != asciiDoubleQuote { |
130 | 25 | throw JSONDecodingError.malformedString |
131 | 8.23k | } |
132 | 8.23k | source.formIndex(after: &index) |
133 | 8.23k | |
134 | 8.23k | // Count the base-64 digits |
135 | 8.23k | // Ignore most unrecognized characters in this first pass, |
136 | 8.23k | // stop at the closing double quote. |
137 | 8.23k | let digitsStart = index |
138 | 8.23k | var rawChars = 0 |
139 | 8.23k | var sawSection4Characters = false |
140 | 8.23k | var sawSection5Characters = false |
141 | 3.13M | while index != end { |
142 | 3.13M | var digit = source[index] |
143 | 3.13M | if digit == asciiDoubleQuote { |
144 | 8.14k | break |
145 | 3.13M | } |
146 | 3.13M | |
147 | 3.13M | if digit == asciiBackslash { |
148 | 313 | source.formIndex(after: &index) |
149 | 313 | if index == end { |
150 | 1 | throw JSONDecodingError.malformedString |
151 | 312 | } |
152 | 312 | let escaped = source[index] |
153 | 312 | switch escaped { |
154 | 312 | case asciiLowerU: |
155 | 1 | // TODO: Parse hex escapes such as \u0041. Note that |
156 | 1 | // such escapes are going to be extremely rare, so |
157 | 1 | // there's little point in optimizing for them. |
158 | 1 | throw JSONDecodingError.malformedString |
159 | 312 | case asciiForwardSlash: |
160 | 296 | digit = escaped |
161 | 312 | default: |
162 | 15 | // Reject \b \f \n \r \t \" or \\ and all illegal escapes |
163 | 15 | throw JSONDecodingError.malformedString |
164 | 312 | } |
165 | 3.13M | } |
166 | 3.13M | |
167 | 3.13M | if digit == asciiPlus || digit == asciiForwardSlash { |
168 | 2.55k | sawSection4Characters = true |
169 | 3.12M | } else if digit == asciiMinus || digit == asciiUnderscore { |
170 | 7.68k | sawSection5Characters = true |
171 | 7.68k | } |
172 | 3.13M | let k = base64Values[Int(digit)] |
173 | 3.13M | if k >= 0 { |
174 | 3.02M | rawChars += 1 |
175 | 3.02M | } |
176 | 3.13M | source.formIndex(after: &index) |
177 | 3.13M | } |
178 | 8.21k | |
179 | 8.21k | // We reached the end without seeing the close quote |
180 | 8.21k | if index == end { |
181 | 76 | throw JSONDecodingError.malformedString |
182 | 8.14k | } |
183 | 8.14k | // Reject mixed encodings. |
184 | 8.14k | if sawSection4Characters && sawSection5Characters { |
185 | 2 | throw JSONDecodingError.malformedString |
186 | 8.13k | } |
187 | 8.13k | |
188 | 8.13k | // Allocate a Data object of exactly the right size |
189 | 8.13k | var value = Data(count: rawChars * 3 / 4) |
190 | 8.13k | |
191 | 8.13k | // Scan the digits again and populate the Data object. |
192 | 8.13k | // In this pass, we check for (and fail) if there are |
193 | 8.13k | // unexpected characters. But we don't check for end-of-input, |
194 | 8.13k | // because the loop above already verified that there was |
195 | 8.13k | // a closing double quote. |
196 | 8.13k | index = digitsStart |
197 | 8.13k | try value.withUnsafeMutableBytes { |
198 | 8.13k | (body: UnsafeMutableRawBufferPointer) in |
199 | 8.13k | if var p = body.baseAddress, body.count > 0 { |
200 | 4.48k | var n = 0 |
201 | 4.48k | var chars = 0 // # chars in current group |
202 | 4.48k | var padding = 0 // # padding '=' chars |
203 | 2.78M | digits: while true { |
204 | 2.78M | let digit = source[index] |
205 | 2.78M | var k = base64Values[Int(digit)] |
206 | 2.78M | if k < 0 { |
207 | 4.80k | switch digit { |
208 | 4.80k | case asciiDoubleQuote: |
209 | 4.00k | break digits |
210 | 4.80k | case asciiBackslash: |
211 | 104 | source.formIndex(after: &index) |
212 | 104 | let escaped = source[index] |
213 | 104 | switch escaped { |
214 | 104 | case asciiForwardSlash: |
215 | 104 | k = base64Values[Int(escaped)] |
216 | 104 | default: |
217 | 0 | // Note: Invalid backslash escapes were caught |
218 | 0 | // above; we should never get here. |
219 | 0 | throw JSONDecodingError.malformedString |
220 | 104 | } |
221 | 4.80k | case asciiSpace: |
222 | 221 | source.formIndex(after: &index) |
223 | 221 | continue digits |
224 | 4.80k | case asciiEqualSign: // Count padding |
225 | 1.44k | while true { |
226 | 1.44k | switch source[index] { |
227 | 1.44k | case asciiDoubleQuote: |
228 | 411 | break digits |
229 | 1.44k | case asciiSpace: |
230 | 202 | break |
231 | 1.44k | case 61: |
232 | 805 | padding += 1 |
233 | 1.44k | default: // Only '=' and whitespace permitted |
234 | 27 | throw JSONDecodingError.malformedString |
235 | 1.44k | } |
236 | 1.00k | source.formIndex(after: &index) |
237 | 1.00k | } |
238 | 4.80k | default: |
239 | 39 | throw JSONDecodingError.malformedString |
240 | 4.80k | } |
241 | 2.77M | } |
242 | 2.77M | n <<= 6 |
243 | 2.77M | n |= k |
244 | 2.77M | chars += 1 |
245 | 2.77M | if chars == 4 { |
246 | 693k | p[0] = UInt8(truncatingIfNeeded: n >> 16) |
247 | 693k | p[1] = UInt8(truncatingIfNeeded: n >> 8) |
248 | 693k | p[2] = UInt8(truncatingIfNeeded: n) |
249 | 693k | p += 3 |
250 | 693k | chars = 0 |
251 | 693k | n = 0 |
252 | 693k | } |
253 | 2.77M | source.formIndex(after: &index) |
254 | 2.77M | } |
255 | 4.41k | switch chars { |
256 | 4.41k | case 3: |
257 | 1.35k | p[0] = UInt8(truncatingIfNeeded: n >> 10) |
258 | 1.35k | p[1] = UInt8(truncatingIfNeeded: n >> 2) |
259 | 1.35k | if padding == 1 || padding == 0 { |
260 | 1.35k | return |
261 | 1.35k | } |
262 | 4.41k | case 2: |
263 | 1.18k | p[0] = UInt8(truncatingIfNeeded: n >> 4) |
264 | 1.18k | if padding == 2 || padding == 0 { |
265 | 1.17k | return |
266 | 1.17k | } |
267 | 4.41k | case 0: |
268 | 1.87k | if padding == 0 { |
269 | 1.86k | return |
270 | 1.86k | } |
271 | 4.41k | default: |
272 | 5 | break |
273 | 4.41k | } |
274 | 23 | throw JSONDecodingError.malformedString |
275 | 4.48k | } |
276 | 8.04k | } |
277 | 8.04k | source.formIndex(after: &index) |
278 | 8.04k | return value |
279 | 8.25k | } |
280 | | |
281 | | // JSON encoding allows a variety of \-escapes, including |
282 | | // escaping UTF-16 code points (which may be surrogate pairs). |
283 | 60.5k | private func decodeString(_ s: String) -> String? { |
284 | 60.5k | var out = String.UnicodeScalarView() |
285 | 60.5k | var chars = s.unicodeScalars.makeIterator() |
286 | 8.78M | while let c = chars.next() { |
287 | 8.78M | switch c.value { |
288 | 8.78M | case UInt32(asciiBackslash): // backslash |
289 | 78.9k | if let escaped = chars.next() { |
290 | 78.9k | switch escaped.value { |
291 | 78.9k | case UInt32(asciiLowerU): // "\u" |
292 | 4.25k | // Exactly 4 hex digits: |
293 | 4.25k | if let digit1 = chars.next(), |
294 | 4.25k | let d1 = fromHexDigit(digit1), |
295 | 4.25k | let digit2 = chars.next(), |
296 | 4.25k | let d2 = fromHexDigit(digit2), |
297 | 4.25k | let digit3 = chars.next(), |
298 | 4.25k | let d3 = fromHexDigit(digit3), |
299 | 4.25k | let digit4 = chars.next(), |
300 | 4.25k | let d4 = fromHexDigit(digit4) |
301 | 4.25k | { |
302 | 4.13k | let codePoint = ((d1 * 16 + d2) * 16 + d3) * 16 + d4 |
303 | 4.13k | if let scalar = UnicodeScalar(codePoint) { |
304 | 3.89k | out.append(scalar) |
305 | 3.89k | } else if codePoint < 0xD800 || codePoint >= 0xE000 { |
306 | 0 | // Not a valid Unicode scalar. |
307 | 0 | return nil |
308 | 241 | } else if codePoint >= 0xDC00 { |
309 | 20 | // Low surrogate without a preceding high surrogate. |
310 | 20 | return nil |
311 | 221 | } else { |
312 | 221 | // We have a high surrogate (in the range 0xD800..<0xDC00), so |
313 | 221 | // verify that it is followed by a low surrogate. |
314 | 221 | guard chars.next() == "\\", chars.next() == "u" else { |
315 | 72 | // High surrogate was not followed by a Unicode escape sequence. |
316 | 72 | return nil |
317 | 149 | } |
318 | 149 | if let digit1 = chars.next(), |
319 | 149 | let d1 = fromHexDigit(digit1), |
320 | 149 | let digit2 = chars.next(), |
321 | 149 | let d2 = fromHexDigit(digit2), |
322 | 149 | let digit3 = chars.next(), |
323 | 149 | let d3 = fromHexDigit(digit3), |
324 | 149 | let digit4 = chars.next(), |
325 | 149 | let d4 = fromHexDigit(digit4) |
326 | 149 | { |
327 | 138 | let follower = ((d1 * 16 + d2) * 16 + d3) * 16 + d4 |
328 | 138 | guard 0xDC00 <= follower && follower < 0xE000 else { |
329 | 21 | // High surrogate was not followed by a low surrogate. |
330 | 21 | return nil |
331 | 117 | } |
332 | 117 | let high = codePoint - 0xD800 |
333 | 117 | let low = follower - 0xDC00 |
334 | 117 | let composed = 0x10000 | high << 10 | low |
335 | 117 | guard let composedScalar = UnicodeScalar(composed) else { |
336 | 0 | // Composed value is not a valid Unicode scalar. |
337 | 0 | return nil |
338 | 117 | } |
339 | 117 | out.append(composedScalar) |
340 | 117 | } else { |
341 | 11 | // Malformed \u escape for low surrogate |
342 | 11 | return nil |
343 | 117 | } |
344 | 4.00k | } |
345 | 4.00k | } else { |
346 | 123 | // Malformed \u escape |
347 | 123 | return nil |
348 | 123 | } |
349 | 78.9k | case UInt32(asciiLowerB): // \b |
350 | 32.5k | out.append("\u{08}") |
351 | 78.9k | case UInt32(asciiLowerF): // \f |
352 | 26.0k | out.append("\u{0c}") |
353 | 78.9k | case UInt32(asciiLowerN): // \n |
354 | 7.29k | out.append("\u{0a}") |
355 | 78.9k | case UInt32(asciiLowerR): // \r |
356 | 1.69k | out.append("\u{0d}") |
357 | 78.9k | case UInt32(asciiLowerT): // \t |
358 | 2.36k | out.append("\u{09}") |
359 | 78.9k | case UInt32(asciiDoubleQuote), UInt32(asciiBackslash), |
360 | 4.68k | UInt32(asciiForwardSlash): // " \ / |
361 | 4.68k | out.append(escaped) |
362 | 78.9k | default: |
363 | 90 | return nil // Unrecognized escape |
364 | 78.9k | } |
365 | 78.6k | } else { |
366 | 0 | return nil // Input ends with backslash |
367 | 0 | } |
368 | 8.78M | default: |
369 | 8.70M | out.append(c) |
370 | 8.78M | } |
371 | 8.78M | } |
372 | 60.1k | return String(out) |
373 | 60.5k | } |
374 | | |
375 | | /// |
376 | | /// The basic scanner support is entirely private |
377 | | /// |
378 | | /// For performance, it works directly against UTF-8 bytes in memory. |
379 | | internal struct JSONScanner { |
380 | | private let source: UnsafeRawBufferPointer |
381 | | private var index: UnsafeRawBufferPointer.Index |
382 | 11.9k | private var numberParser = DoubleParser() |
383 | | internal let options: JSONDecodingOptions |
384 | | internal let extensions: any ExtensionMap |
385 | | internal var recursionBudget: Int |
386 | | |
387 | | /// True if the scanner has read all of the data from the source, with the |
388 | | /// exception of any trailing whitespace (which is consumed by reading this |
389 | | /// property). |
390 | | internal var complete: Bool { |
391 | 18 | mutating get { |
392 | 18 | skipWhitespace() |
393 | 18 | return !hasMoreContent |
394 | 18 | } |
395 | | } |
396 | | |
397 | | /// True if the scanner has not yet reached the end of the source. |
398 | 48.2M | private var hasMoreContent: Bool { |
399 | 48.2M | index != source.endIndex |
400 | 48.2M | } |
401 | | |
402 | | /// The byte (UTF-8 code unit) at the scanner's current position. |
403 | 86.3M | private var currentByte: UInt8 { |
404 | 86.3M | source[index] |
405 | 86.3M | } |
406 | | |
407 | | internal init( |
408 | | source: UnsafeRawBufferPointer, |
409 | | options: JSONDecodingOptions, |
410 | | extensions: (any ExtensionMap)? |
411 | 1.13k | ) { |
412 | 1.13k | self.source = source |
413 | 1.13k | self.index = source.startIndex |
414 | 1.13k | self.recursionBudget = options.messageDepthLimit |
415 | 1.13k | self.options = options |
416 | 1.13k | self.extensions = extensions ?? SimpleExtensionMap() |
417 | 1.13k | } |
418 | | |
419 | 8.71k | internal mutating func incrementRecursionDepth() throws { |
420 | 8.71k | recursionBudget -= 1 |
421 | 8.71k | if recursionBudget < 0 { |
422 | 0 | throw JSONDecodingError.messageDepthLimit |
423 | 8.71k | } |
424 | 8.71k | } |
425 | | |
426 | 4.21k | internal mutating func decrementRecursionDepth() { |
427 | 4.21k | recursionBudget += 1 |
428 | 4.21k | // This should never happen, if it does, something is probably corrupting memory, and |
429 | 4.21k | // simply throwing doesn't make much sense. |
430 | 4.21k | if recursionBudget > options.messageDepthLimit { |
431 | 0 | fatalError("Somehow JSONDecoding unwound more objects than it started") |
432 | 0 | } |
433 | 4.21k | } |
434 | | |
435 | | /// Advances the scanner to the next position in the source. |
436 | 36.7M | private mutating func advance() { |
437 | 36.7M | source.formIndex(after: &index) |
438 | 36.7M | } |
439 | | |
440 | | /// Skip whitespace. |
441 | 7.31M | private mutating func skipWhitespace() { |
442 | 7.86M | while hasMoreContent { |
443 | 7.86M | let u = currentByte |
444 | 7.86M | switch u { |
445 | 7.86M | case asciiSpace, asciiTab, asciiNewLine, asciiCarriageReturn: |
446 | 546k | advance() |
447 | 7.86M | default: |
448 | 7.31M | return |
449 | 7.86M | } |
450 | 546k | } |
451 | 6.25k | } |
452 | | |
453 | | /// Returns (but does not consume) the next non-whitespace |
454 | | /// character. This is used by google.protobuf.Value, for |
455 | | /// example, for custom JSON parsing. |
456 | 44 | internal mutating func peekOneCharacter() throws -> Character { |
457 | 44 | skipWhitespace() |
458 | 44 | guard hasMoreContent else { |
459 | 0 | throw JSONDecodingError.truncated |
460 | 44 | } |
461 | 44 | return Character(UnicodeScalar(UInt32(currentByte))!) |
462 | 44 | } |
463 | | |
464 | | // Parse the leading UInt64 from the provided utf8 bytes. |
465 | | // |
466 | | // This is called in three different situations: |
467 | | // |
468 | | // * Unquoted number. |
469 | | // |
470 | | // * Simple quoted number. If a number is quoted but has no |
471 | | // backslashes, the caller can use this directly on the UTF8 by |
472 | | // just verifying the quote marks. This code returns `nil` if it |
473 | | // sees a backslash, in which case the caller will need to handle ... |
474 | | // |
475 | | // * Complex quoted number. In this case, the caller must parse the |
476 | | // quoted value as a string, then convert the string to utf8 and |
477 | | // use this to parse the result. This is slow but fortunately |
478 | | // rare. |
479 | | // |
480 | | // In the common case where the number is written in integer form, |
481 | | // this code does a simple straight conversion. If the number is in |
482 | | // floating-point format, this uses a slower and less accurate |
483 | | // approach: it identifies a substring comprising a float, and then |
484 | | // uses Double() and UInt64() to convert that string to an unsigned |
485 | | // integer. In particular, it cannot preserve full 64-bit integer |
486 | | // values when they are written in floating-point format. |
487 | | // |
488 | | // If it encounters a "\" backslash character, it returns a nil. This |
489 | | // is used by callers that are parsing quoted numbers. See nextSInt() |
490 | | // and nextUInt() below. |
491 | | private func parseBareUInt64( |
492 | | source: UnsafeRawBufferPointer, |
493 | | index: inout UnsafeRawBufferPointer.Index, |
494 | | end: UnsafeRawBufferPointer.Index |
495 | 94.5k | ) throws -> UInt64? { |
496 | 94.5k | if index == end { |
497 | 10 | throw JSONDecodingError.truncated |
498 | 94.5k | } |
499 | 94.5k | let start = index |
500 | 94.5k | let c = source[index] |
501 | 94.5k | switch c { |
502 | 94.5k | case asciiZero: // 0 |
503 | 40.7k | source.formIndex(after: &index) |
504 | 40.7k | if index != end { |
505 | 40.7k | let after = source[index] |
506 | 40.7k | switch after { |
507 | 40.7k | case asciiZero...asciiNine: // 0...9 |
508 | 3 | // leading '0' forbidden unless it is the only digit |
509 | 3 | throw JSONDecodingError.leadingZero |
510 | 40.7k | case asciiPeriod, asciiLowerE, asciiUpperE: // . e |
511 | 4.87k | // Slow path: JSON numbers can be written in floating-point notation |
512 | 4.87k | index = start |
513 | 4.87k | if let d = try parseBareDouble( |
514 | 4.87k | source: source, |
515 | 4.87k | index: &index, |
516 | 4.87k | end: end |
517 | 4.87k | ) { |
518 | 4.86k | if let u = UInt64(exactly: d) { |
519 | 4.85k | return u |
520 | 4.85k | } |
521 | 3 | } |
522 | 3 | throw JSONDecodingError.malformedNumber |
523 | 40.7k | case asciiBackslash: |
524 | 4 | return nil |
525 | 40.7k | default: |
526 | 35.8k | return 0 |
527 | 40.7k | } |
528 | 40.7k | } |
529 | 34 | return 0 |
530 | 94.5k | case asciiOne...asciiNine: // 1...9 |
531 | 53.6k | var n = 0 as UInt64 |
532 | 135k | while index != end { |
533 | 135k | let digit = source[index] |
534 | 135k | switch digit { |
535 | 135k | case asciiZero...asciiNine: // 0...9 |
536 | 82.1k | let val = UInt64(digit - asciiZero) |
537 | 82.1k | if n > UInt64.max / 10 || n * 10 > UInt64.max - val { |
538 | 6 | throw JSONDecodingError.numberRange |
539 | 82.1k | } |
540 | 82.1k | source.formIndex(after: &index) |
541 | 82.1k | n = n * 10 + val |
542 | 135k | case asciiPeriod, asciiLowerE, asciiUpperE: // . e |
543 | 4.13k | // Slow path: JSON allows floating-point notation for integers |
544 | 4.13k | index = start |
545 | 4.13k | if let d = try parseBareDouble( |
546 | 4.13k | source: source, |
547 | 4.13k | index: &index, |
548 | 4.13k | end: end |
549 | 4.13k | ) { |
550 | 4.09k | if let u = UInt64(exactly: d) { |
551 | 4.08k | return u |
552 | 4.08k | } |
553 | 7 | } |
554 | 7 | throw JSONDecodingError.malformedNumber |
555 | 135k | case asciiBackslash: |
556 | 12 | return nil |
557 | 135k | default: |
558 | 48.7k | return n |
559 | 135k | } |
560 | 82.1k | } |
561 | 678 | return n |
562 | 94.5k | case asciiBackslash: |
563 | 21 | return nil |
564 | 94.5k | default: |
565 | 120 | throw JSONDecodingError.malformedNumber |
566 | 94.5k | } |
567 | 94.5k | } |
568 | | |
569 | | // Parse the leading Int64 from the provided utf8. |
570 | | // |
571 | | // This uses parseBareUInt64() to do the heavy lifting; |
572 | | // we just check for a leading minus and negate the result |
573 | | // as necessary. |
574 | | // |
575 | | // As with parseBareUInt64(), if it encounters a "\" backslash |
576 | | // character, it returns a nil. This allows callers to use this to |
577 | | // do a "fast-path" decode of simple quoted numbers by parsing the |
578 | | // UTF8 directly, only falling back to a full String decode when |
579 | | // absolutely necessary. |
580 | | private func parseBareSInt64( |
581 | | source: UnsafeRawBufferPointer, |
582 | | index: inout UnsafeRawBufferPointer.Index, |
583 | | end: UnsafeRawBufferPointer.Index |
584 | 52.2k | ) throws -> Int64? { |
585 | 52.2k | if index == end { |
586 | 23 | throw JSONDecodingError.truncated |
587 | 52.2k | } |
588 | 52.2k | let c = source[index] |
589 | 52.2k | if c == asciiMinus { // - |
590 | 9.08k | source.formIndex(after: &index) |
591 | 9.08k | if index == end { |
592 | 7 | throw JSONDecodingError.truncated |
593 | 9.08k | } |
594 | 9.08k | // character after '-' must be digit |
595 | 9.08k | let digit = source[index] |
596 | 9.08k | if digit < asciiZero || digit > asciiNine { |
597 | 8 | throw JSONDecodingError.malformedNumber |
598 | 9.07k | } |
599 | 9.07k | if let n = try parseBareUInt64(source: source, index: &index, end: end) { |
600 | 9.06k | let limit: UInt64 = 0x8000_0000_0000_0000 // -Int64.min |
601 | 9.06k | if n >= limit { |
602 | 63 | if n > limit { |
603 | 62 | // Too large negative number |
604 | 62 | throw JSONDecodingError.numberRange |
605 | 62 | } else { |
606 | 1 | return Int64.min // Special case for Int64.min |
607 | 1 | } |
608 | 8.99k | } |
609 | 8.99k | return -Int64(bitPattern: n) |
610 | 9.06k | } else { |
611 | 5 | return nil |
612 | 5 | } |
613 | 43.0k | } else if let n = try parseBareUInt64(source: source, index: &index, end: end) { |
614 | 42.9k | if n > UInt64(bitPattern: Int64.max) { |
615 | 44 | throw JSONDecodingError.numberRange |
616 | 42.9k | } |
617 | 42.9k | return Int64(bitPattern: n) |
618 | 42.9k | } else { |
619 | 15 | return nil |
620 | 15 | } |
621 | 52.2k | } |
622 | | |
623 | | // Identify a floating-point token in the upcoming UTF8 bytes. |
624 | | // |
625 | | // This implements the full grammar defined by the JSON RFC 7159. |
626 | | // Note that Swift's string-to-number conversions are much more |
627 | | // lenient, so this is necessary if we want to accurately reject |
628 | | // malformed JSON numbers. |
629 | | // |
630 | | // This is used by nextDouble() and nextFloat() to parse double and |
631 | | // floating-point values, including values that happen to be in quotes. |
632 | | // It's also used by the slow path in parseBareSInt64() and parseBareUInt64() |
633 | | // above to handle integer values that are written in float-point notation. |
634 | | private func parseBareDouble( |
635 | | source: UnsafeRawBufferPointer, |
636 | | index: inout UnsafeRawBufferPointer.Index, |
637 | | end: UnsafeRawBufferPointer.Index |
638 | 252k | ) throws -> Double? { |
639 | 252k | // RFC 7159 defines the grammar for JSON numbers as: |
640 | 252k | // number = [ minus ] int [ frac ] [ exp ] |
641 | 252k | if index == end { |
642 | 2 | throw JSONDecodingError.truncated |
643 | 252k | } |
644 | 252k | let start = index |
645 | 252k | var c = source[index] |
646 | 252k | if c == asciiBackslash { |
647 | 21 | return nil |
648 | 252k | } |
649 | 252k | |
650 | 252k | // Optional leading minus sign |
651 | 252k | if c == asciiMinus { // - |
652 | 4.73k | source.formIndex(after: &index) |
653 | 4.73k | if index == end { |
654 | 4 | index = start |
655 | 4 | throw JSONDecodingError.truncated |
656 | 4.73k | } |
657 | 4.73k | c = source[index] |
658 | 4.73k | if c == asciiBackslash { |
659 | 4 | return nil |
660 | 4.73k | } |
661 | 248k | } else if c == asciiUpperN { // Maybe NaN? |
662 | 6 | // Return nil, let the caller deal with it. |
663 | 6 | return nil |
664 | 252k | } |
665 | 252k | |
666 | 252k | if c == asciiUpperI { // Maybe Infinity, Inf, -Infinity, or -Inf ? |
667 | 10 | // Return nil, let the caller deal with it. |
668 | 10 | return nil |
669 | 252k | } |
670 | 252k | |
671 | 252k | // Integer part can be zero or a series of digits not starting with zero |
672 | 252k | // int = zero / (digit1-9 *DIGIT) |
673 | 252k | switch c { |
674 | 252k | case asciiZero: |
675 | 132k | // First digit can be zero only if not followed by a digit |
676 | 132k | source.formIndex(after: &index) |
677 | 132k | if index == end { |
678 | 12 | return 0.0 |
679 | 132k | } |
680 | 132k | c = source[index] |
681 | 132k | if c == asciiBackslash { |
682 | 4 | return nil |
683 | 132k | } |
684 | 132k | if c >= asciiZero && c <= asciiNine { |
685 | 11 | throw JSONDecodingError.leadingZero |
686 | 11 | } |
687 | 252k | case asciiOne...asciiNine: |
688 | 704k | while c >= asciiZero && c <= asciiNine { |
689 | 584k | source.formIndex(after: &index) |
690 | 584k | if index == end { |
691 | 58 | if let d = numberParser.utf8ToDouble(bytes: source, start: start, end: index) { |
692 | 54 | return d |
693 | 54 | } else { |
694 | 4 | throw JSONDecodingError.invalidUTF8 |
695 | 4 | } |
696 | 584k | } |
697 | 584k | c = source[index] |
698 | 584k | if c == asciiBackslash { |
699 | 25 | return nil |
700 | 584k | } |
701 | 584k | } |
702 | 252k | default: |
703 | 254 | // Integer part cannot be empty |
704 | 254 | throw JSONDecodingError.malformedNumber |
705 | 252k | } |
706 | 252k | |
707 | 252k | // frac = decimal-point 1*DIGIT |
708 | 252k | if c == asciiPeriod { |
709 | 1.73k | source.formIndex(after: &index) |
710 | 1.73k | if index == end { |
711 | 7 | // decimal point must have a following digit |
712 | 7 | throw JSONDecodingError.truncated |
713 | 1.72k | } |
714 | 1.72k | c = source[index] |
715 | 1.72k | switch c { |
716 | 1.72k | case asciiZero...asciiNine: // 0...9 |
717 | 2.02M | while c >= asciiZero && c <= asciiNine { |
718 | 2.02M | source.formIndex(after: &index) |
719 | 2.02M | if index == end { |
720 | 31 | if let d = numberParser.utf8ToDouble(bytes: source, start: start, end: index) { |
721 | 24 | return d |
722 | 24 | } else { |
723 | 7 | throw JSONDecodingError.invalidUTF8 |
724 | 7 | } |
725 | 2.02M | } |
726 | 2.02M | c = source[index] |
727 | 2.02M | if c == asciiBackslash { |
728 | 13 | return nil |
729 | 2.02M | } |
730 | 2.02M | } |
731 | 1.72k | case asciiBackslash: |
732 | 4 | return nil |
733 | 1.72k | default: |
734 | 19 | // decimal point must be followed by at least one digit |
735 | 19 | throw JSONDecodingError.malformedNumber |
736 | 1.72k | } |
737 | 252k | } |
738 | 252k | |
739 | 252k | // exp = e [ minus / plus ] 1*DIGIT |
740 | 252k | if c == asciiLowerE || c == asciiUpperE { |
741 | 28.2k | source.formIndex(after: &index) |
742 | 28.2k | if index == end { |
743 | 14 | // "e" must be followed by +,-, or digit |
744 | 14 | throw JSONDecodingError.truncated |
745 | 28.2k | } |
746 | 28.2k | c = source[index] |
747 | 28.2k | if c == asciiBackslash { |
748 | 4 | return nil |
749 | 28.2k | } |
750 | 28.2k | if c == asciiPlus || c == asciiMinus { // + - |
751 | 1.45k | source.formIndex(after: &index) |
752 | 1.45k | if index == end { |
753 | 2 | // must be at least one digit in exponent |
754 | 2 | throw JSONDecodingError.truncated |
755 | 1.45k | } |
756 | 1.45k | c = source[index] |
757 | 1.45k | if c == asciiBackslash { |
758 | 4 | return nil |
759 | 1.44k | } |
760 | 28.2k | } |
761 | 28.2k | switch c { |
762 | 28.2k | case asciiZero...asciiNine: |
763 | 406k | while c >= asciiZero && c <= asciiNine { |
764 | 378k | source.formIndex(after: &index) |
765 | 378k | if index == end { |
766 | 70 | if let d = numberParser.utf8ToDouble(bytes: source, start: start, end: index) { |
767 | 65 | return d |
768 | 65 | } else { |
769 | 5 | throw JSONDecodingError.invalidUTF8 |
770 | 5 | } |
771 | 378k | } |
772 | 378k | c = source[index] |
773 | 378k | if c == asciiBackslash { |
774 | 4 | return nil |
775 | 378k | } |
776 | 378k | } |
777 | 28.2k | default: |
778 | 47 | // must be at least one digit in exponent |
779 | 47 | throw JSONDecodingError.malformedNumber |
780 | 28.2k | } |
781 | 252k | } |
782 | 252k | if let d = numberParser.utf8ToDouble(bytes: source, start: start, end: index) { |
783 | 252k | return d |
784 | 252k | } else { |
785 | 38 | throw JSONDecodingError.invalidUTF8 |
786 | 38 | } |
787 | 252k | } |
788 | | |
789 | | /// Returns a fully-parsed string with all backslash escapes |
790 | | /// correctly processed, or nil if next token is not a string. |
791 | | /// |
792 | | /// Assumes the leading quote has been verified (but not consumed) |
793 | 79.7k | private mutating func parseOptionalQuotedString() -> String? { |
794 | 79.7k | // Caller has already asserted that currentByte == quote here |
795 | 79.7k | var sawBackslash = false |
796 | 79.7k | advance() |
797 | 79.7k | let start = index |
798 | 10.2M | while hasMoreContent { |
799 | 10.2M | switch currentByte { |
800 | 10.2M | case asciiDoubleQuote: // " |
801 | 79.4k | let s = utf8ToString(bytes: source, start: start, end: index) |
802 | 79.4k | advance() |
803 | 79.4k | if let t = s { |
804 | 79.4k | if sawBackslash { |
805 | 60.5k | return decodeString(t) |
806 | 60.5k | } else { |
807 | 18.9k | return t |
808 | 18.9k | } |
809 | 79.4k | } else { |
810 | 15 | return nil // Invalid UTF8 |
811 | 15 | } |
812 | 10.2M | case asciiBackslash: // \ |
813 | 79.2k | advance() |
814 | 79.2k | guard hasMoreContent else { |
815 | 19 | return nil // Unterminated escape |
816 | 79.2k | } |
817 | 79.2k | sawBackslash = true |
818 | 10.2M | case 0..<asciiSpace: |
819 | 249 | // Unescaped control characters (U+0000...U+001F) are not |
820 | 249 | // allowed inside a JSON string; they must be escaped. |
821 | 249 | return nil |
822 | 10.2M | default: |
823 | 10.1M | break |
824 | 10.2M | } |
825 | 10.1M | advance() |
826 | 10.1M | } |
827 | 73 | return nil // Unterminated quoted string |
828 | 79.7k | } |
829 | | |
830 | | /// Parse an unsigned integer, whether or not its quoted. |
831 | | /// This also handles cases such as quoted numbers that have |
832 | | /// backslash escapes in them. |
833 | | /// |
834 | | /// This supports the full range of UInt64 (whether quoted or not) |
835 | | /// unless the number is written in floating-point format. In that |
836 | | /// case, we decode it with only Double precision. |
837 | 0 | internal mutating func nextUInt() throws -> UInt64 { |
838 | 0 | skipWhitespace() |
839 | 0 | guard hasMoreContent else { |
840 | 0 | throw JSONDecodingError.truncated |
841 | 0 | } |
842 | 0 | let c = currentByte |
843 | 0 | if c == asciiDoubleQuote { |
844 | 0 | let start = index |
845 | 0 | advance() |
846 | 0 | if let u = try parseBareUInt64( |
847 | 0 | source: source, |
848 | 0 | index: &index, |
849 | 0 | end: source.endIndex |
850 | 0 | ) { |
851 | 0 | guard hasMoreContent else { |
852 | 0 | throw JSONDecodingError.truncated |
853 | 0 | } |
854 | 0 | if currentByte != asciiDoubleQuote { |
855 | 0 | throw JSONDecodingError.malformedNumber |
856 | 0 | } |
857 | 0 | advance() |
858 | 0 | return u |
859 | 0 | } else { |
860 | 0 | // Couldn't parse because it had a "\" in the string, |
861 | 0 | // so parse out the quoted string and then reparse |
862 | 0 | // the result to get a UInt. |
863 | 0 | index = start |
864 | 0 | let s = try nextQuotedString() |
865 | 0 | let raw = s.data(using: String.Encoding.utf8)! |
866 | 12 | let n = try raw.withUnsafeBytes { |
867 | 12 | (body: UnsafeRawBufferPointer) -> UInt64? in |
868 | 12 | if body.count > 0 { |
869 | 12 | var index = body.startIndex |
870 | 12 | let end = body.endIndex |
871 | 12 | if let u = try parseBareUInt64( |
872 | 12 | source: body, |
873 | 12 | index: &index, |
874 | 12 | end: end |
875 | 12 | ) { |
876 | 1 | if index == end { |
877 | 0 | return u |
878 | 1 | } |
879 | 4 | } |
880 | 4 | } |
881 | 4 | return nil |
882 | 12 | } |
883 | 0 | if let n = n { |
884 | 0 | return n |
885 | 0 | } |
886 | 0 | } |
887 | 0 | } else if let u = try parseBareUInt64( |
888 | 0 | source: source, |
889 | 0 | index: &index, |
890 | 0 | end: source.endIndex |
891 | 0 | ) { |
892 | 0 | return u |
893 | 0 | } |
894 | 0 | throw JSONDecodingError.malformedNumber |
895 | 0 | } |
896 | | |
897 | | /// Parse a signed integer, quoted or not, including handling |
898 | | /// backslash escapes for quoted values. |
899 | | /// |
900 | | /// This supports the full range of Int64 (whether quoted or not) |
901 | | /// unless the number is written in floating-point format. In that |
902 | | /// case, we decode it with only Double precision. |
903 | 2 | internal mutating func nextSInt() throws -> Int64 { |
904 | 2 | skipWhitespace() |
905 | 2 | guard hasMoreContent else { |
906 | 0 | throw JSONDecodingError.truncated |
907 | 2 | } |
908 | 2 | let c = currentByte |
909 | 2 | if c == asciiDoubleQuote { |
910 | 1 | let start = index |
911 | 1 | advance() |
912 | 1 | if let s = try parseBareSInt64( |
913 | 1 | source: source, |
914 | 1 | index: &index, |
915 | 1 | end: source.endIndex |
916 | 1 | ) { |
917 | 1 | guard hasMoreContent else { |
918 | 0 | throw JSONDecodingError.truncated |
919 | 1 | } |
920 | 1 | if currentByte != asciiDoubleQuote { |
921 | 0 | throw JSONDecodingError.malformedNumber |
922 | 1 | } |
923 | 1 | advance() |
924 | 1 | return s |
925 | 1 | } else { |
926 | 0 | // Couldn't parse because it had a "\" in the string, |
927 | 0 | // so parse out the quoted string and then reparse |
928 | 0 | // the result as an SInt. |
929 | 0 | index = start |
930 | 0 | let s = try nextQuotedString() |
931 | 0 | let raw = s.data(using: String.Encoding.utf8)! |
932 | 9 | let n = try raw.withUnsafeBytes { |
933 | 9 | (body: UnsafeRawBufferPointer) -> Int64? in |
934 | 9 | if body.count > 0 { |
935 | 9 | var index = body.startIndex |
936 | 9 | let end = body.endIndex |
937 | 9 | if let s = try parseBareSInt64( |
938 | 9 | source: body, |
939 | 9 | index: &index, |
940 | 9 | end: end |
941 | 9 | ) { |
942 | 2 | if index == end { |
943 | 0 | return s |
944 | 2 | } |
945 | 8 | } |
946 | 8 | } |
947 | 8 | return nil |
948 | 9 | } |
949 | 0 | if let n = n { |
950 | 0 | return n |
951 | 0 | } |
952 | 0 | } |
953 | 0 | } else if let s = try parseBareSInt64( |
954 | 2 | source: source, |
955 | 2 | index: &index, |
956 | 2 | end: source.endIndex |
957 | 2 | ) { |
958 | 1 | return s |
959 | 1 | } |
960 | 0 | throw JSONDecodingError.malformedNumber |
961 | 2 | } |
962 | | |
963 | | /// Parse the next Float value, regardless of whether it |
964 | | /// is quoted, including handling backslash escapes for |
965 | | /// quoted strings. |
966 | 0 | internal mutating func nextFloat() throws -> Float { |
967 | 0 | skipWhitespace() |
968 | 0 | guard hasMoreContent else { |
969 | 0 | throw JSONDecodingError.truncated |
970 | 0 | } |
971 | 0 | let c = currentByte |
972 | 0 | if c == asciiDoubleQuote { // " |
973 | 0 | let start = index |
974 | 0 | advance() |
975 | 0 | if let d = try parseBareDouble( |
976 | 0 | source: source, |
977 | 0 | index: &index, |
978 | 0 | end: source.endIndex |
979 | 0 | ) { |
980 | 0 | guard hasMoreContent else { |
981 | 0 | throw JSONDecodingError.truncated |
982 | 0 | } |
983 | 0 | if currentByte != asciiDoubleQuote { |
984 | 0 | throw JSONDecodingError.malformedNumber |
985 | 0 | } |
986 | 0 | advance() |
987 | 0 | // A value that parses as a finite Double can still be out of |
988 | 0 | // range for Float (for example "1e39"), in which case the |
989 | 0 | // conversion yields an infinity. Reject it, matching the |
990 | 0 | // unquoted path below. |
991 | 0 | let f = Float(d) |
992 | 0 | if f.isFinite { |
993 | 0 | return f |
994 | 0 | } |
995 | 0 | throw JSONDecodingError.malformedNumber |
996 | 0 | } else { |
997 | 0 | // Slow Path: parseBareDouble returned nil: It might be |
998 | 0 | // a valid float, but had something that |
999 | 0 | // parseBareDouble cannot directly handle. So we reset, |
1000 | 0 | // try a full string parse, then examine the result: |
1001 | 0 | index = start |
1002 | 0 | let s = try nextQuotedString() |
1003 | 0 | switch s { |
1004 | 0 | case "NaN": return Float.nan |
1005 | 0 | case "Inf": return Float.infinity |
1006 | 0 | case "-Inf": return -Float.infinity |
1007 | 0 | case "Infinity": return Float.infinity |
1008 | 0 | case "-Infinity": return -Float.infinity |
1009 | 0 | default: |
1010 | 0 | let raw = s.data(using: String.Encoding.utf8)! |
1011 | 38 | let n = try raw.withUnsafeBytes { |
1012 | 38 | (body: UnsafeRawBufferPointer) -> Float? in |
1013 | 38 | if body.count > 0 { |
1014 | 38 | var index = body.startIndex |
1015 | 38 | let end = body.endIndex |
1016 | 38 | if let d = try parseBareDouble( |
1017 | 38 | source: body, |
1018 | 38 | index: &index, |
1019 | 38 | end: end |
1020 | 38 | ) { |
1021 | 17 | let f = Float(d) |
1022 | 17 | if index == end && f.isFinite { |
1023 | 0 | return f |
1024 | 17 | } |
1025 | 30 | } |
1026 | 30 | } |
1027 | 30 | return nil |
1028 | 38 | } |
1029 | 0 | if let n = n { |
1030 | 0 | return n |
1031 | 0 | } |
1032 | 0 | } |
1033 | 0 | } |
1034 | 0 | } else { |
1035 | 0 | if let d = try parseBareDouble( |
1036 | 0 | source: source, |
1037 | 0 | index: &index, |
1038 | 0 | end: source.endIndex |
1039 | 0 | ) { |
1040 | 0 | let f = Float(d) |
1041 | 0 | if f.isFinite { |
1042 | 0 | return f |
1043 | 0 | } |
1044 | 0 | } |
1045 | 0 | } |
1046 | 0 | throw JSONDecodingError.malformedNumber |
1047 | 0 | } |
1048 | | |
1049 | | /// Parse the next Double value, regardless of whether it |
1050 | | /// is quoted, including handling backslash escapes for |
1051 | | /// quoted strings. |
1052 | 5 | internal mutating func nextDouble() throws -> Double { |
1053 | 5 | skipWhitespace() |
1054 | 5 | guard hasMoreContent else { |
1055 | 0 | throw JSONDecodingError.truncated |
1056 | 5 | } |
1057 | 5 | let c = currentByte |
1058 | 5 | if c == asciiDoubleQuote { // " |
1059 | 1 | let start = index |
1060 | 1 | advance() |
1061 | 1 | if let d = try parseBareDouble( |
1062 | 1 | source: source, |
1063 | 1 | index: &index, |
1064 | 1 | end: source.endIndex |
1065 | 1 | ) { |
1066 | 0 | guard hasMoreContent else { |
1067 | 0 | throw JSONDecodingError.truncated |
1068 | 0 | } |
1069 | 0 | if currentByte != asciiDoubleQuote { |
1070 | 0 | throw JSONDecodingError.malformedNumber |
1071 | 0 | } |
1072 | 0 | advance() |
1073 | 0 | return d |
1074 | 1 | } else { |
1075 | 1 | // Slow Path: parseBareDouble returned nil: It might be |
1076 | 1 | // a valid float, but had something that |
1077 | 1 | // parseBareDouble cannot directly handle. So we reset, |
1078 | 1 | // try a full string parse, then examine the result: |
1079 | 1 | index = start |
1080 | 1 | let s = try nextQuotedString() |
1081 | 1 | switch s { |
1082 | 1 | case "NaN": return Double.nan |
1083 | 1 | case "Inf": return Double.infinity |
1084 | 1 | case "-Inf": return -Double.infinity |
1085 | 1 | case "Infinity": return Double.infinity |
1086 | 1 | case "-Infinity": return -Double.infinity |
1087 | 1 | default: |
1088 | 1 | let raw = s.data(using: String.Encoding.utf8)! |
1089 | 18 | let n = try raw.withUnsafeBytes { |
1090 | 18 | (body: UnsafeRawBufferPointer) -> Double? in |
1091 | 18 | if body.count > 0 { |
1092 | 18 | var index = body.startIndex |
1093 | 18 | let end = body.endIndex |
1094 | 18 | if let d = try parseBareDouble( |
1095 | 18 | source: body, |
1096 | 18 | index: &index, |
1097 | 18 | end: end |
1098 | 18 | ) { |
1099 | 12 | if index == end { |
1100 | 0 | return d |
1101 | 12 | } |
1102 | 15 | } |
1103 | 15 | } |
1104 | 15 | return nil |
1105 | 18 | } |
1106 | 0 | if let n = n { |
1107 | 0 | return n |
1108 | 0 | } |
1109 | 1 | } |
1110 | 0 | } |
1111 | 4 | } else { |
1112 | 4 | if let d = try parseBareDouble( |
1113 | 4 | source: source, |
1114 | 4 | index: &index, |
1115 | 4 | end: source.endIndex |
1116 | 4 | ) { |
1117 | 2 | return d |
1118 | 2 | } |
1119 | 0 | } |
1120 | 0 | throw JSONDecodingError.malformedNumber |
1121 | 5 | } |
1122 | | |
1123 | | /// Return the contents of the following quoted string, |
1124 | | /// or throw an error if the next token is not a string. |
1125 | 801 | internal mutating func nextQuotedString() throws -> String { |
1126 | 801 | skipWhitespace() |
1127 | 801 | guard hasMoreContent else { |
1128 | 0 | throw JSONDecodingError.truncated |
1129 | 801 | } |
1130 | 801 | let c = currentByte |
1131 | 801 | if c != asciiDoubleQuote { |
1132 | 18 | throw JSONDecodingError.malformedString |
1133 | 783 | } |
1134 | 783 | if let s = parseOptionalQuotedString() { |
1135 | 774 | return s |
1136 | 774 | } else { |
1137 | 9 | throw JSONDecodingError.malformedString |
1138 | 9 | } |
1139 | 783 | } |
1140 | | |
1141 | | /// Return the contents of the following quoted string, |
1142 | | /// or nil if the next token is not a string. |
1143 | | /// This will only throw an error if the next token starts |
1144 | | /// out as a string but is malformed in some way. |
1145 | 0 | internal mutating func nextOptionalQuotedString() throws -> String? { |
1146 | 0 | skipWhitespace() |
1147 | 0 | guard hasMoreContent else { |
1148 | 0 | return nil |
1149 | 0 | } |
1150 | 0 | let c = currentByte |
1151 | 0 | if c != asciiDoubleQuote { |
1152 | 0 | return nil |
1153 | 0 | } |
1154 | 0 | return try nextQuotedString() |
1155 | 0 | } |
1156 | | |
1157 | | /// Return a Data with the decoded contents of the |
1158 | | /// following base-64 string. |
1159 | | /// |
1160 | | /// Notes on Google's implementation: |
1161 | | /// * Google's C++ implementation accepts arbitrary whitespace |
1162 | | /// mixed in with the base-64 characters |
1163 | | /// * Google's C++ implementation ignores missing '=' characters |
1164 | | /// but if present, there must be the exact correct number of them. |
1165 | | /// * Google's C++ implementation accepts both "regular" and |
1166 | | /// "web-safe" base-64 variants (it seems to prefer the |
1167 | | /// web-safe version as defined in RFC 4648 |
1168 | 0 | internal mutating func nextBytesValue() throws -> Data { |
1169 | 0 | skipWhitespace() |
1170 | 0 | guard hasMoreContent else { |
1171 | 0 | throw JSONDecodingError.truncated |
1172 | 0 | } |
1173 | 0 | return try parseBytes(source: source, index: &index, end: source.endIndex) |
1174 | 0 | } |
1175 | | |
1176 | | /// Private function to help parse keywords. |
1177 | 62.0k | private mutating func skipOptionalKeyword(bytes: [UInt8]) -> Bool { |
1178 | 62.0k | let start = index |
1179 | 248k | for b in bytes { |
1180 | 248k | guard hasMoreContent else { |
1181 | 31 | index = start |
1182 | 31 | return false |
1183 | 248k | } |
1184 | 248k | let c = currentByte |
1185 | 248k | if c != b { |
1186 | 99 | index = start |
1187 | 99 | return false |
1188 | 248k | } |
1189 | 248k | advance() |
1190 | 248k | } |
1191 | 61.8k | if hasMoreContent { |
1192 | 61.8k | let c = currentByte |
1193 | 61.8k | if (c >= asciiUpperA && c <= asciiUpperZ) || (c >= asciiLowerA && c <= asciiLowerZ) { |
1194 | 24 | index = start |
1195 | 24 | return false |
1196 | 61.7k | } |
1197 | 61.8k | } |
1198 | 61.8k | return true |
1199 | 62.0k | } |
1200 | | |
1201 | | /// If the next token is the identifier "null", consume it and return true. |
1202 | 696 | internal mutating func skipOptionalNull() -> Bool { |
1203 | 696 | skipWhitespace() |
1204 | 332k | if hasMoreContent && currentByte == asciiLowerN { |
1205 | 4 | return skipOptionalKeyword(bytes: [ |
1206 | 4 | asciiLowerN, asciiLowerU, asciiLowerL, asciiLowerL, |
1207 | 4 | ]) |
1208 | 692 | } |
1209 | 692 | return false |
1210 | 696 | } |
1211 | | |
1212 | | /// Return the following Bool "true" or "false", including |
1213 | | /// full processing of quoted boolean values. (Used in map |
1214 | | /// keys, for instance.) |
1215 | 0 | internal mutating func nextBool() throws -> Bool { |
1216 | 0 | skipWhitespace() |
1217 | 0 | guard hasMoreContent else { |
1218 | 0 | throw JSONDecodingError.truncated |
1219 | 0 | } |
1220 | 0 | let c = currentByte |
1221 | 0 | switch c { |
1222 | 0 | case asciiLowerF: // f |
1223 | 0 | if skipOptionalKeyword(bytes: [ |
1224 | 0 | asciiLowerF, asciiLowerA, asciiLowerL, asciiLowerS, asciiLowerE, |
1225 | 0 | ]) { |
1226 | 0 | return false |
1227 | 0 | } |
1228 | 0 | case asciiLowerT: // t |
1229 | 0 | if skipOptionalKeyword(bytes: [ |
1230 | 0 | asciiLowerT, asciiLowerR, asciiLowerU, asciiLowerE, |
1231 | 0 | ]) { |
1232 | 0 | return true |
1233 | 0 | } |
1234 | 0 | default: |
1235 | 0 | break |
1236 | 0 | } |
1237 | 0 | throw JSONDecodingError.malformedBool |
1238 | 0 | } |
1239 | | |
1240 | | /// Return the following Bool "true" or "false", including |
1241 | | /// full processing of quoted boolean values. (Used in map |
1242 | | /// keys, for instance.) |
1243 | 0 | internal mutating func nextQuotedBool() throws -> Bool { |
1244 | 0 | skipWhitespace() |
1245 | 0 | guard hasMoreContent else { |
1246 | 0 | throw JSONDecodingError.truncated |
1247 | 0 | } |
1248 | 0 | if currentByte != asciiDoubleQuote { |
1249 | 0 | throw JSONDecodingError.unquotedMapKey |
1250 | 0 | } |
1251 | 0 | if let s = parseOptionalQuotedString() { |
1252 | 0 | switch s { |
1253 | 0 | case "false": return false |
1254 | 0 | case "true": return true |
1255 | 0 | default: break |
1256 | 0 | } |
1257 | 0 | } |
1258 | 0 | throw JSONDecodingError.malformedBool |
1259 | 0 | } |
1260 | | |
1261 | | /// Returns pointer/count spanning the UTF8 bytes of the next regular |
1262 | | /// key or nil if the key contains a backslash (and therefore requires |
1263 | | /// the full string-parsing logic to properly parse). |
1264 | 829k | private mutating func nextOptionalKey() throws -> UnsafeRawBufferPointer? { |
1265 | 829k | skipWhitespace() |
1266 | 829k | let stringStart = index |
1267 | 829k | guard hasMoreContent else { |
1268 | 109 | throw JSONDecodingError.truncated |
1269 | 829k | } |
1270 | 829k | if currentByte != asciiDoubleQuote { |
1271 | 385 | return nil |
1272 | 829k | } |
1273 | 829k | advance() |
1274 | 829k | let nameStart = index |
1275 | 20.1M | while hasMoreContent && currentByte != asciiDoubleQuote { |
1276 | 19.3M | if currentByte == asciiBackslash || currentByte < asciiSpace { |
1277 | 60.0k | // Backslash escapes go through the slow path; unescaped control |
1278 | 60.0k | // characters are invalid there and get rejected uniformly. |
1279 | 60.0k | index = stringStart // Reset to open quote |
1280 | 60.0k | return nil |
1281 | 19.2M | } |
1282 | 19.2M | advance() |
1283 | 19.2M | } |
1284 | 769k | guard hasMoreContent else { |
1285 | 650 | throw JSONDecodingError.truncated |
1286 | 768k | } |
1287 | 768k | let buff = UnsafeRawBufferPointer( |
1288 | 768k | start: source.baseAddress! + nameStart, |
1289 | 768k | count: index - nameStart |
1290 | 768k | ) |
1291 | 768k | advance() |
1292 | 768k | return buff |
1293 | 829k | } |
1294 | | |
1295 | | /// Parse a field name, look it up in the provided field name map, |
1296 | | /// and return the corresponding field number. |
1297 | | /// |
1298 | | /// Throws if field name cannot be parsed. |
1299 | | /// If it encounters an unknown field name, it throws |
1300 | | /// unless `options.ignoreUnknownFields` is set, in which case |
1301 | | /// it silently skips it. |
1302 | | internal mutating func nextFieldNumber( |
1303 | | names: _NameMap, |
1304 | | messageType: any Message.Type |
1305 | 118 | ) throws -> Int? { |
1306 | 118 | while true { |
1307 | 118 | var fieldName: String |
1308 | 118 | if let key = try nextOptionalKey() { |
1309 | 112 | // Fast path: We parsed it as UTF8 bytes... |
1310 | 112 | try skipRequiredCharacter(asciiColon) // : |
1311 | 112 | if let fieldNumber = names.number(forJSONName: key) { |
1312 | 45 | return fieldNumber |
1313 | 67 | } |
1314 | 67 | if let s = utf8ToString(bytes: key.baseAddress!, count: key.count) { |
1315 | 67 | fieldName = s |
1316 | 67 | } else { |
1317 | 0 | throw JSONDecodingError.invalidUTF8 |
1318 | 67 | } |
1319 | 67 | } else { |
1320 | 6 | // Slow path: We parsed a String; lookups from String are slower. |
1321 | 6 | fieldName = try nextQuotedString() |
1322 | 3 | try skipRequiredCharacter(asciiColon) // : |
1323 | 3 | if let fieldNumber = names.number(forJSONName: fieldName) { |
1324 | 0 | return fieldNumber |
1325 | 3 | } |
1326 | 70 | } |
1327 | 70 | if let first = fieldName.utf8.first, first == UInt8(ascii: "["), |
1328 | 70 | let last = fieldName.utf8.last, last == UInt8(ascii: "]") |
1329 | 70 | { |
1330 | 70 | fieldName.removeFirst() |
1331 | 70 | fieldName.removeLast() |
1332 | 70 | if let fieldNumber = extensions.fieldNumberForProto(messageType: messageType, protoFieldName: fieldName) |
1333 | 70 | { |
1334 | 0 | return fieldNumber |
1335 | 70 | } |
1336 | 70 | } |
1337 | 70 | if !options.ignoreUnknownFields { |
1338 | 70 | throw JSONDecodingError.unknownField(fieldName) |
1339 | 70 | } |
1340 | 0 | // Unknown field, skip it and try to parse the next field name |
1341 | 0 | try skipValue() |
1342 | 0 | if skipOptionalObjectEnd() { |
1343 | 0 | return nil |
1344 | 0 | } |
1345 | 0 | try skipRequiredComma() |
1346 | 0 | } |
1347 | 0 | } |
1348 | | |
1349 | | /// Parse the next token as a string or numeric enum value. Throws |
1350 | | /// unrecognizedEnumValue if the string/number can't initialize the |
1351 | | /// enum. Will throw other errors if the JSON is malformed. |
1352 | 0 | internal mutating func nextEnumValue<E: Enum>() throws -> E? { |
1353 | 6.90k | func throwOrIgnore() throws -> E? { |
1354 | 6.90k | if options.ignoreUnknownFields { |
1355 | 6.74k | return nil |
1356 | 6.74k | } else { |
1357 | 161 | throw JSONDecodingError.unrecognizedEnumValue |
1358 | 161 | } |
1359 | 6.90k | } |
1360 | 0 | skipWhitespace() |
1361 | 0 | guard hasMoreContent else { |
1362 | 0 | throw JSONDecodingError.truncated |
1363 | 0 | } |
1364 | 0 | if currentByte == asciiDoubleQuote { |
1365 | 0 | if let name = try nextOptionalKey() { |
1366 | 0 | if let e = E(rawUTF8: name) { |
1367 | 0 | return e |
1368 | 0 | } else { |
1369 | 0 | return try throwOrIgnore() |
1370 | 0 | } |
1371 | 0 | } |
1372 | 0 | let name = try nextQuotedString() |
1373 | 0 | if let e = E(name: name) { |
1374 | 0 | return e |
1375 | 0 | } else { |
1376 | 0 | return try throwOrIgnore() |
1377 | 0 | } |
1378 | 0 | } else { |
1379 | 0 | let n = try nextSInt() |
1380 | 0 | // Enum values are int32 on the wire; reject anything outside |
1381 | 0 | // that range like the text format decoder does. |
1382 | 18.0k | guard n >= Int64(Int32.min) && n <= Int64(Int32.max) else { |
1383 | 0 | throw JSONDecodingError.numberRange |
1384 | 0 | } |
1385 | 0 | if let e = E(rawValue: Int(n)) { |
1386 | 0 | return e |
1387 | 0 | } else { |
1388 | 0 | return try throwOrIgnore() |
1389 | 0 | } |
1390 | 0 | } |
1391 | 0 | } |
1392 | | |
1393 | | /// Helper for skipping a single-character token. |
1394 | 2.28M | private mutating func skipRequiredCharacter(_ required: UInt8) throws { |
1395 | 2.28M | skipWhitespace() |
1396 | 2.28M | guard hasMoreContent else { |
1397 | 1.62k | throw JSONDecodingError.truncated |
1398 | 2.28M | } |
1399 | 2.28M | let next = currentByte |
1400 | 2.28M | if next == required { |
1401 | 2.28M | advance() |
1402 | 2.28M | return |
1403 | 2.28M | } |
1404 | 2.13k | throw JSONDecodingError.failure |
1405 | 2.28M | } |
1406 | | |
1407 | | /// Skip "{", throw if that's not the next character. |
1408 | 3.29k | internal mutating func skipRequiredObjectStart() throws { |
1409 | 3.29k | try skipRequiredCharacter(asciiOpenCurlyBracket) // { |
1410 | 3.16k | try incrementRecursionDepth() |
1411 | 3.16k | } |
1412 | | |
1413 | | /// Skip ",", throw if that's not the next character. |
1414 | 1.44k | internal mutating func skipRequiredComma() throws { |
1415 | 1.44k | try skipRequiredCharacter(asciiComma) |
1416 | 1.44k | } |
1417 | | |
1418 | | /// Skip ":", throw if that's not the next character. |
1419 | 1.29k | internal mutating func skipRequiredColon() throws { |
1420 | 1.29k | try skipRequiredCharacter(asciiColon) |
1421 | 688 | } |
1422 | | |
1423 | | /// Skip "[", throw if that's not the next character. |
1424 | 4 | internal mutating func skipRequiredArrayStart() throws { |
1425 | 4 | try skipRequiredCharacter(asciiOpenSquareBracket) // [ |
1426 | 4 | } |
1427 | | |
1428 | | /// Helper for skipping optional single-character tokens. |
1429 | 2.71M | private mutating func skipOptionalCharacter(_ c: UInt8) -> Bool { |
1430 | 2.71M | skipWhitespace() |
1431 | 2.71M | if hasMoreContent && currentByte == c { |
1432 | 1.04M | advance() |
1433 | 1.04M | return true |
1434 | 1.67M | } |
1435 | 1.67M | return false |
1436 | 2.71M | } |
1437 | | |
1438 | | /// If the next non-whitespace character is "[", skip it |
1439 | | /// and return true. Otherwise, return false. |
1440 | 0 | internal mutating func skipOptionalArrayStart() -> Bool { |
1441 | 0 | skipOptionalCharacter(asciiOpenSquareBracket) |
1442 | 0 | } |
1443 | | |
1444 | | /// If the next non-whitespace character is "]", skip it |
1445 | | /// and return true. Otherwise, return false. |
1446 | 18 | internal mutating func skipOptionalArrayEnd() -> Bool { |
1447 | 18 | skipOptionalCharacter(asciiCloseSquareBracket) // ]// ] |
1448 | 18 | } |
1449 | | |
1450 | | /// If the next non-whitespace character is "}", skip it |
1451 | | /// and return true. Otherwise, return false. |
1452 | 8.97k | internal mutating func skipOptionalObjectEnd() -> Bool { |
1453 | 8.97k | let result = skipOptionalCharacter(asciiCloseCurlyBracket) // } |
1454 | 8.97k | if result { |
1455 | 1.05k | decrementRecursionDepth() |
1456 | 1.05k | } |
1457 | 8.97k | return result |
1458 | 8.97k | } |
1459 | | |
1460 | | /// Return the next complete JSON structure as a string. |
1461 | | /// For example, this might return "true", or "123.456", |
1462 | | /// or "{\"foo\": 7, \"bar\": [8, 9]}" |
1463 | | /// |
1464 | | /// Used by Any to get the upcoming JSON value as a string. |
1465 | | /// Note: The value might be an object or array. |
1466 | 0 | internal mutating func skip() throws -> String { |
1467 | 0 | skipWhitespace() |
1468 | 0 | let start = index |
1469 | 0 | try skipValue() |
1470 | 0 | if let s = utf8ToString(bytes: source, start: start, end: index) { |
1471 | 0 | return s |
1472 | 0 | } else { |
1473 | 0 | throw JSONDecodingError.invalidUTF8 |
1474 | 0 | } |
1475 | 0 | } |
1476 | | |
1477 | | /// Advance index past the next value. This is used |
1478 | | /// by skip() and by unknown field handling. |
1479 | | /// Note: This handles objects {...} recursively but arrays [...] non-recursively |
1480 | | /// This avoids us requiring excessive stack space for deeply nested |
1481 | | /// arrays (which are not included in the recursion budget check). |
1482 | 630k | private mutating func skipValue() throws { |
1483 | 630k | skipWhitespace() |
1484 | 630k | var totalArrayDepth = 0 |
1485 | 658k | while true { |
1486 | 658k | var arrayDepth = 0 |
1487 | 1.18M | while skipOptionalArrayStart() { |
1488 | 526k | arrayDepth += 1 |
1489 | 658k | } |
1490 | 658k | guard hasMoreContent else { |
1491 | 130 | throw JSONDecodingError.truncated |
1492 | 658k | } |
1493 | 658k | switch currentByte { |
1494 | 658k | case asciiDoubleQuote: // " begins a string |
1495 | 11.0k | try skipString() |
1496 | 658k | case asciiOpenCurlyBracket: // { begins an object |
1497 | 376k | try skipObject() |
1498 | 658k | case asciiCloseSquareBracket: // ] ends an empty array |
1499 | 7.86k | if arrayDepth == 0 { |
1500 | 3 | throw JSONDecodingError.failure |
1501 | 7.86k | } |
1502 | 7.86k | // We also close out [[]] or [[[]]] here |
1503 | 17.8k | while arrayDepth > 0 && skipOptionalArrayEnd() { |
1504 | 10.0k | arrayDepth -= 1 |
1505 | 10.0k | } |
1506 | 658k | case asciiLowerN: // n must be null |
1507 | 29.4k | if !skipOptionalKeyword(bytes: [ |
1508 | 29.4k | asciiLowerN, asciiLowerU, asciiLowerL, asciiLowerL, |
1509 | 29.4k | ]) { |
1510 | 15 | throw JSONDecodingError.truncated |
1511 | 15 | } |
1512 | 658k | case asciiLowerF: // f must be false |
1513 | 333 | if !skipOptionalKeyword(bytes: [ |
1514 | 333 | asciiLowerF, asciiLowerA, asciiLowerL, asciiLowerS, asciiLowerE, |
1515 | 333 | ]) { |
1516 | 13 | throw JSONDecodingError.truncated |
1517 | 13 | } |
1518 | 658k | case asciiLowerT: // t must be true |
1519 | 211 | if !skipOptionalKeyword(bytes: [ |
1520 | 211 | asciiLowerT, asciiLowerR, asciiLowerU, asciiLowerE, |
1521 | 211 | ]) { |
1522 | 13 | throw JSONDecodingError.truncated |
1523 | 13 | } |
1524 | 658k | default: // everything else is a number token |
1525 | 232k | _ = try nextDouble() |
1526 | 658k | } |
1527 | 657k | totalArrayDepth += arrayDepth |
1528 | 667k | while totalArrayDepth > 0 && skipOptionalArrayEnd() { |
1529 | 9.69k | totalArrayDepth -= 1 |
1530 | 657k | } |
1531 | 657k | if totalArrayDepth > 0 { |
1532 | 28.0k | try skipRequiredComma() |
1533 | 629k | } else { |
1534 | 629k | return |
1535 | 629k | } |
1536 | 28.0k | } |
1537 | 0 | } |
1538 | | |
1539 | | /// Advance the index past the next complete {...} construct. |
1540 | 376k | private mutating func skipObject() throws { |
1541 | 376k | try skipRequiredObjectStart() |
1542 | 376k | if skipOptionalObjectEnd() { |
1543 | 366k | return |
1544 | 366k | } |
1545 | 45.0k | while true { |
1546 | 45.0k | skipWhitespace() |
1547 | 45.0k | try skipString() |
1548 | 44.9k | try skipRequiredColon() |
1549 | 44.9k | try skipValue() |
1550 | 44.4k | if skipOptionalObjectEnd() { |
1551 | 9.63k | return |
1552 | 34.8k | } |
1553 | 34.8k | try skipRequiredComma() |
1554 | 34.8k | } |
1555 | 0 | } |
1556 | | |
1557 | | /// Advance the index past the next complete quoted string. |
1558 | | /// |
1559 | | // Caveat: This does not fully validate; it will accept |
1560 | | // strings that have malformed \ escapes. |
1561 | | // |
1562 | | // It would be nice to do better, but I don't think it's critical, |
1563 | | // since there are many reasons that strings (and other tokens for |
1564 | | // that matter) may be skippable but not parsable. For example: |
1565 | | // Old clients that don't know new field types will skip fields |
1566 | | // they don't know; newer clients may reject the same input due to |
1567 | | // schema mismatches or other issues. |
1568 | 56.0k | private mutating func skipString() throws { |
1569 | 56.0k | guard hasMoreContent else { |
1570 | 15 | throw JSONDecodingError.truncated |
1571 | 56.0k | } |
1572 | 56.0k | if currentByte != asciiDoubleQuote { |
1573 | 61 | throw JSONDecodingError.malformedString |
1574 | 55.9k | } |
1575 | 55.9k | advance() |
1576 | 1.16M | while hasMoreContent { |
1577 | 1.16M | let c = currentByte |
1578 | 1.16M | switch c { |
1579 | 1.16M | case asciiDoubleQuote: |
1580 | 55.9k | advance() |
1581 | 55.9k | return |
1582 | 1.16M | case asciiBackslash: |
1583 | 4.83k | advance() |
1584 | 4.83k | guard hasMoreContent else { |
1585 | 1 | throw JSONDecodingError.truncated |
1586 | 4.83k | } |
1587 | 4.83k | advance() |
1588 | 1.16M | default: |
1589 | 1.09M | advance() |
1590 | 1.16M | } |
1591 | 1.10M | } |
1592 | 67 | throw JSONDecodingError.truncated |
1593 | 56.0k | } |
1594 | | } |