/src/skia/src/utils/SkJSON.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * Copyright 2018 Google Inc. |
3 | | * |
4 | | * Use of this source code is governed by a BSD-style license that can be |
5 | | * found in the LICENSE file. |
6 | | */ |
7 | | |
8 | | #include "src/utils/SkJSON.h" |
9 | | |
10 | | #include "include/core/SkData.h" |
11 | | #include "include/core/SkRefCnt.h" |
12 | | #include "include/core/SkStream.h" |
13 | | #include "include/core/SkString.h" |
14 | | #include "include/private/base/SkDebug.h" |
15 | | #include "include/private/base/SkMalloc.h" |
16 | | #include "include/private/base/SkTo.h" |
17 | | #include "include/utils/SkParse.h" |
18 | | #include "src/base/SkArenaAlloc.h" |
19 | | #include "src/base/SkUTF.h" |
20 | | |
21 | | #include <cmath> |
22 | | #include <cstdint> |
23 | | #include <cstdlib> |
24 | | #include <limits> |
25 | | #include <new> |
26 | | #include <tuple> |
27 | | #include <vector> |
28 | | |
29 | | namespace skjson { |
30 | | |
31 | | // #define SK_JSON_REPORT_ERRORS |
32 | | |
33 | | static_assert( sizeof(Value) == 8, ""); |
34 | | static_assert(alignof(Value) == 8, ""); |
35 | | |
36 | | static constexpr size_t kRecAlign = alignof(Value); |
37 | | |
38 | 12.2M | void Value::init_tagged(Tag t) { |
39 | 12.2M | memset(fData8, 0, sizeof(fData8)); |
40 | 12.2M | fData8[0] = SkTo<uint8_t>(t); |
41 | 12.2M | SkASSERT(this->getTag() == t); |
42 | 12.2M | } |
43 | | |
44 | | // Pointer values store a type (in the lower kTagBits bits) and a pointer. |
45 | 6.21M | void Value::init_tagged_pointer(Tag t, void* p) { |
46 | 6.21M | if (sizeof(Value) == sizeof(uintptr_t)) { |
47 | 6.21M | *this->cast<uintptr_t>() = reinterpret_cast<uintptr_t>(p); |
48 | | // For 64-bit, we rely on the pointer lower bits being zero. |
49 | 6.21M | SkASSERT(!(fData8[0] & kTagMask)); |
50 | 6.21M | fData8[0] |= SkTo<uint8_t>(t); |
51 | 6.21M | } else { |
52 | | // For 32-bit, we store the pointer in the upper word |
53 | 0 | SkASSERT(sizeof(Value) == sizeof(uintptr_t) * 2); |
54 | 0 | this->init_tagged(t); |
55 | 0 | *this->cast<uintptr_t>() = reinterpret_cast<uintptr_t>(p); |
56 | 0 | } |
57 | | |
58 | 6.21M | SkASSERT(this->getTag() == t); |
59 | 6.21M | SkASSERT(this->ptr<void>() == p); |
60 | 6.21M | } |
61 | | |
62 | 3.24k | NullValue::NullValue() { |
63 | 3.24k | this->init_tagged(Tag::kNull); |
64 | 3.24k | SkASSERT(this->getTag() == Tag::kNull); |
65 | 3.24k | } |
66 | | |
67 | 94.1k | BoolValue::BoolValue(bool b) { |
68 | 94.1k | this->init_tagged(Tag::kBool); |
69 | 94.1k | *this->cast<bool>() = b; |
70 | 94.1k | SkASSERT(this->getTag() == Tag::kBool); |
71 | 94.1k | } |
72 | | |
73 | 10.1M | NumberValue::NumberValue(int32_t i) { |
74 | 10.1M | this->init_tagged(Tag::kInt); |
75 | 10.1M | *this->cast<int32_t>() = i; |
76 | 10.1M | SkASSERT(this->getTag() == Tag::kInt); |
77 | 10.1M | } |
78 | | |
79 | 1.96M | NumberValue::NumberValue(float f) { |
80 | 1.96M | this->init_tagged(Tag::kFloat); |
81 | 1.96M | *this->cast<float>() = f; |
82 | 1.96M | SkASSERT(this->getTag() == Tag::kFloat); |
83 | 1.96M | } |
84 | | |
85 | | // Vector recs point to externally allocated slabs with the following layout: |
86 | | // |
87 | | // [size_t n] [REC_0] ... [REC_n-1] [optional extra trailing storage] |
88 | | // |
89 | | // Long strings use extra_alloc_size == 1 to store the \0 terminator. |
90 | | // |
91 | | template <typename T, size_t extra_alloc_size = 0> |
92 | 6.21M | static void* MakeVector(size_t vec_size, const void* src, size_t src_size, SkArenaAlloc& alloc) { |
93 | | // The Ts are already in memory, so their size should be safe. |
94 | 6.21M | const auto total_size = sizeof(size_t) + vec_size * sizeof(T) + extra_alloc_size; |
95 | 6.21M | auto* size_ptr = reinterpret_cast<size_t*>(alloc.makeBytesAlignedTo(total_size, kRecAlign)); |
96 | | |
97 | 6.21M | *size_ptr = vec_size; |
98 | 6.21M | sk_careful_memcpy(size_ptr + 1, src, src_size * sizeof(T)); |
99 | | |
100 | 6.21M | return size_ptr; |
101 | 6.21M | } SkJSON.cpp:void* skjson::MakeVector<char, 1ul>(unsigned long, void const*, unsigned long, SkArenaAlloc&) Line | Count | Source | 92 | 723k | static void* MakeVector(size_t vec_size, const void* src, size_t src_size, SkArenaAlloc& alloc) { | 93 | | // The Ts are already in memory, so their size should be safe. | 94 | 723k | const auto total_size = sizeof(size_t) + vec_size * sizeof(T) + extra_alloc_size; | 95 | 723k | auto* size_ptr = reinterpret_cast<size_t*>(alloc.makeBytesAlignedTo(total_size, kRecAlign)); | 96 | | | 97 | 723k | *size_ptr = vec_size; | 98 | 723k | sk_careful_memcpy(size_ptr + 1, src, src_size * sizeof(T)); | 99 | | | 100 | 723k | return size_ptr; | 101 | 723k | } |
SkJSON.cpp:void* skjson::MakeVector<skjson::Value, 0ul>(unsigned long, void const*, unsigned long, SkArenaAlloc&) Line | Count | Source | 92 | 4.30M | static void* MakeVector(size_t vec_size, const void* src, size_t src_size, SkArenaAlloc& alloc) { | 93 | | // The Ts are already in memory, so their size should be safe. | 94 | 4.30M | const auto total_size = sizeof(size_t) + vec_size * sizeof(T) + extra_alloc_size; | 95 | 4.30M | auto* size_ptr = reinterpret_cast<size_t*>(alloc.makeBytesAlignedTo(total_size, kRecAlign)); | 96 | | | 97 | 4.30M | *size_ptr = vec_size; | 98 | 4.30M | sk_careful_memcpy(size_ptr + 1, src, src_size * sizeof(T)); | 99 | | | 100 | 4.30M | return size_ptr; | 101 | 4.30M | } |
SkJSON.cpp:void* skjson::MakeVector<skjson::Member, 0ul>(unsigned long, void const*, unsigned long, SkArenaAlloc&) Line | Count | Source | 92 | 1.18M | static void* MakeVector(size_t vec_size, const void* src, size_t src_size, SkArenaAlloc& alloc) { | 93 | | // The Ts are already in memory, so their size should be safe. | 94 | 1.18M | const auto total_size = sizeof(size_t) + vec_size * sizeof(T) + extra_alloc_size; | 95 | 1.18M | auto* size_ptr = reinterpret_cast<size_t*>(alloc.makeBytesAlignedTo(total_size, kRecAlign)); | 96 | | | 97 | 1.18M | *size_ptr = vec_size; | 98 | 1.18M | sk_careful_memcpy(size_ptr + 1, src, src_size * sizeof(T)); | 99 | | | 100 | 1.18M | return size_ptr; | 101 | 1.18M | } |
|
102 | | |
103 | | template <typename T, size_t extra_alloc_size = 0> |
104 | 6.21M | static void* MakeVector(size_t vec_size, const void* src, SkArenaAlloc& alloc) { |
105 | 6.21M | return MakeVector<T, extra_alloc_size>(vec_size, src, vec_size, alloc); |
106 | 6.21M | } SkJSON.cpp:void* skjson::MakeVector<char, 1ul>(unsigned long, void const*, SkArenaAlloc&) Line | Count | Source | 104 | 723k | static void* MakeVector(size_t vec_size, const void* src, SkArenaAlloc& alloc) { | 105 | 723k | return MakeVector<T, extra_alloc_size>(vec_size, src, vec_size, alloc); | 106 | 723k | } |
SkJSON.cpp:void* skjson::MakeVector<skjson::Value, 0ul>(unsigned long, void const*, SkArenaAlloc&) Line | Count | Source | 104 | 4.30M | static void* MakeVector(size_t vec_size, const void* src, SkArenaAlloc& alloc) { | 105 | 4.30M | return MakeVector<T, extra_alloc_size>(vec_size, src, vec_size, alloc); | 106 | 4.30M | } |
SkJSON.cpp:void* skjson::MakeVector<skjson::Member, 0ul>(unsigned long, void const*, SkArenaAlloc&) Line | Count | Source | 104 | 1.18M | static void* MakeVector(size_t vec_size, const void* src, SkArenaAlloc& alloc) { | 105 | 1.18M | return MakeVector<T, extra_alloc_size>(vec_size, src, vec_size, alloc); | 106 | 1.18M | } |
|
107 | | |
108 | 4.30M | ArrayValue::ArrayValue(const Value* src, size_t size, SkArenaAlloc& alloc) { |
109 | 4.30M | this->init_tagged_pointer(Tag::kArray, MakeVector<Value>(size, src, alloc)); |
110 | 4.30M | SkASSERT(this->getTag() == Tag::kArray); |
111 | 4.30M | } |
112 | | |
113 | | // Strings have two flavors: |
114 | | // |
115 | | // -- short strings (len <= 7) -> these are stored inline, in the record |
116 | | // (one byte reserved for null terminator/type): |
117 | | // |
118 | | // [str] [\0]|[max_len - actual_len] |
119 | | // |
120 | | // Storing [max_len - actual_len] allows the 'len' field to double-up as a |
121 | | // null terminator when size == max_len (this works 'cause kShortString == 0). |
122 | | // |
123 | | // -- long strings (len > 7) -> these are externally allocated vectors (VectorRec<char>). |
124 | | // |
125 | | // The string data plus a null-char terminator are copied over. |
126 | | // |
127 | | namespace { |
128 | | |
129 | | // An internal string builder with a fast 8 byte short string load path |
130 | | // (for the common case where the string is not at the end of the stream). |
131 | | class FastString final : public Value { |
132 | | public: |
133 | 6.42M | FastString(const char* src, size_t size, const char* eos, SkArenaAlloc& alloc) { |
134 | 6.42M | SkASSERT(src <= eos); |
135 | | |
136 | 6.42M | if (size > kMaxInlineStringSize) { |
137 | 723k | this->initLongString(src, size, alloc); |
138 | 723k | SkASSERT(this->getTag() == Tag::kString); |
139 | 723k | return; |
140 | 723k | } |
141 | | |
142 | | // initFastShortString is faster (doh), but requires access to 6 chars past src. |
143 | 5.70M | if (src && src + 6 <= eos) { |
144 | 5.67M | this->initFastShortString(src, size); |
145 | 5.67M | } else { |
146 | 27.7k | this->initShortString(src, size); |
147 | 27.7k | } |
148 | | |
149 | 5.70M | SkASSERT(this->getTag() == Tag::kShortString); |
150 | 5.70M | } |
151 | | |
152 | | private: |
153 | | // first byte reserved for tagging, \0 terminator => 6 usable chars |
154 | | inline static constexpr size_t kMaxInlineStringSize = sizeof(Value) - 2; |
155 | | |
156 | 723k | void initLongString(const char* src, size_t size, SkArenaAlloc& alloc) { |
157 | 723k | SkASSERT(size > kMaxInlineStringSize); |
158 | | |
159 | 723k | this->init_tagged_pointer(Tag::kString, MakeVector<char, 1>(size, src, alloc)); |
160 | | |
161 | 723k | auto* data = this->cast<VectorValue<char, Value::Type::kString>>()->begin(); |
162 | 723k | const_cast<char*>(data)[size] = '\0'; |
163 | 723k | } |
164 | | |
165 | 27.7k | void initShortString(const char* src, size_t size) { |
166 | 27.7k | SkASSERT(size <= kMaxInlineStringSize); |
167 | | |
168 | 27.7k | this->init_tagged(Tag::kShortString); |
169 | 27.7k | sk_careful_memcpy(this->cast<char>(), src, size); |
170 | | // Null terminator provided by init_tagged() above (fData8 is zero-initialized). |
171 | 27.7k | } |
172 | | |
173 | 5.67M | void initFastShortString(const char* src, size_t size) { |
174 | 5.67M | SkASSERT(size <= kMaxInlineStringSize); |
175 | | |
176 | 5.67M | uint64_t* s64 = this->cast<uint64_t>(); |
177 | | |
178 | | // Load 8 chars and mask out the tag and \0 terminator. |
179 | | // Note: we picked kShortString == 0 to avoid setting explicitly below. |
180 | 5.67M | static_assert(SkToU8(Tag::kShortString) == 0, "please don't break this"); |
181 | | |
182 | | // Since the first byte is occupied by the tag, we want the string chars [0..5] to land |
183 | | // on bytes [1..6] => the fastest way is to read8 @(src - 1) (always safe, because the |
184 | | // string requires a " prefix at the very least). |
185 | 5.67M | memcpy(s64, src - 1, 8); |
186 | | |
187 | 5.67M | #if defined(SK_CPU_LENDIAN) |
188 | | // The mask for a max-length string (6), with a leading tag and trailing \0 is |
189 | | // 0x00ffffffffffff00. Accounting for the final left-shift, this becomes |
190 | | // 0x0000ffffffffffff. |
191 | 5.67M | *s64 &= (0x0000ffffffffffffULL >> ((kMaxInlineStringSize - size) * 8)) // trailing \0s |
192 | 5.67M | << 8; // tag byte |
193 | | #else |
194 | | static_assert(false, "Big-endian builds are not supported at this time."); |
195 | | #endif |
196 | 5.67M | } |
197 | | }; |
198 | | |
199 | | } // namespace |
200 | | |
201 | | StringValue::StringValue(const char* src, SkArenaAlloc& alloc) |
202 | 0 | : StringValue(src, strlen(src), alloc) {} |
203 | | |
204 | 0 | StringValue::StringValue(const char* src, size_t size, SkArenaAlloc& alloc) { |
205 | 0 | new (this) FastString(src, size, src, alloc); |
206 | 0 | } |
207 | | |
208 | 1.18M | ObjectValue::ObjectValue(const Member* src, size_t size, SkArenaAlloc& alloc) { |
209 | 1.18M | this->init_tagged_pointer(Tag::kObject, MakeVector<Member>(size, src, alloc)); |
210 | 1.18M | SkASSERT(this->getTag() == Tag::kObject); |
211 | 1.18M | } |
212 | | |
213 | | |
214 | | // Boring public Value glue. |
215 | | |
216 | 197M | static int inline_strcmp(const char a[], const char b[]) { |
217 | 228M | for (;;) { |
218 | 228M | char c = *a++; |
219 | 228M | if (c == 0) { |
220 | 18.2M | break; |
221 | 18.2M | } |
222 | 210M | if (c != *b++) { |
223 | 179M | return 1; |
224 | 179M | } |
225 | 210M | } |
226 | 18.2M | return *b != 0; |
227 | 197M | } |
228 | | |
229 | 51.1M | const Member* ObjectValue::find(const char* key) const { |
230 | | // Reverse search for duplicates resolution (policy: return last). |
231 | 51.1M | const auto* begin = this->begin(); |
232 | 51.1M | const auto* member = this->end(); |
233 | | |
234 | 231M | while (member > begin) { |
235 | 197M | --member; |
236 | 197M | if (0 == inline_strcmp(key, member->fKey.as<StringValue>().begin())) { |
237 | 17.4M | return member; |
238 | 17.4M | } |
239 | 197M | } |
240 | | |
241 | 33.7M | return nullptr; |
242 | 51.1M | } |
243 | | |
244 | 0 | Value& ObjectValue::writable(const char* key, SkArenaAlloc& alloc) const { |
245 | 0 | Member* writable_member = const_cast<Member*>(this->find(key)); |
246 | |
|
247 | 0 | if (!writable_member) { |
248 | 0 | ObjectValue* writable_obj = const_cast<ObjectValue*>(this); |
249 | 0 | writable_obj->init_tagged_pointer(Tag::kObject, MakeVector<Member>(this->size() + 1, |
250 | 0 | this->begin(), |
251 | 0 | this->size(), |
252 | 0 | alloc)); |
253 | 0 | writable_member = const_cast<Member*>(writable_obj->end() - 1); |
254 | 0 | writable_member->fKey = StringValue(key, strlen(key), alloc); |
255 | 0 | writable_member->fValue = NullValue(); |
256 | 0 | } |
257 | | |
258 | |
|
259 | 0 | return writable_member->fValue; |
260 | 0 | } |
261 | | |
262 | | namespace { |
263 | | |
264 | | // Lexer/parser inspired by rapidjson [1], sajson [2] and pjson [3]. |
265 | | // |
266 | | // [1] https://github.com/Tencent/rapidjson/ |
267 | | // [2] https://github.com/chadaustin/sajson |
268 | | // [3] https://pastebin.com/hnhSTL3h |
269 | | |
270 | | |
271 | | // bit 0 (0x01) - plain ASCII string character |
272 | | // bit 1 (0x02) - whitespace |
273 | | // bit 2 (0x04) - string terminator (" \\ \0 [control chars] **AND } ]** <- see matchString notes) |
274 | | // bit 3 (0x08) - 0-9 |
275 | | // bit 4 (0x10) - 0-9 e E . |
276 | | // bit 5 (0x20) - scope terminator (} ]) |
277 | | static constexpr uint8_t g_token_flags[256] = { |
278 | | // 0 1 2 3 4 5 6 7 8 9 A B C D E F |
279 | | 4, 4, 4, 4, 4, 4, 4, 4, 4, 6, 6, 4, 4, 6, 4, 4, // 0 |
280 | | 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, // 1 |
281 | | 3, 1, 4, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0x11,1, // 2 |
282 | | 0x19,0x19,0x19,0x19,0x19,0x19,0x19,0x19, 0x19,0x19, 1, 1, 1, 1, 1, 1, // 3 |
283 | | 1, 1, 1, 1, 1, 0x11,1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 4 |
284 | | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4,0x25, 1, 1, // 5 |
285 | | 1, 1, 1, 1, 1, 0x11,1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 6 |
286 | | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,0x25, 1, 1, // 7 |
287 | | |
288 | | // 128-255 |
289 | | 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, |
290 | | 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, |
291 | | 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, |
292 | | 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0 |
293 | | }; |
294 | | |
295 | 77.7M | static inline bool is_ws(char c) { return g_token_flags[static_cast<uint8_t>(c)] & 0x02; } |
296 | 138M | static inline bool is_eostring(char c) { return g_token_flags[static_cast<uint8_t>(c)] & 0x04; } |
297 | 42.9M | static inline bool is_digit(char c) { return g_token_flags[static_cast<uint8_t>(c)] & 0x08; } |
298 | 14.2M | static inline bool is_numeric(char c) { return g_token_flags[static_cast<uint8_t>(c)] & 0x10; } |
299 | 1.64M | static inline bool is_eoscope(char c) { return g_token_flags[static_cast<uint8_t>(c)] & 0x20; } |
300 | | |
301 | 77.1M | static inline const char* skip_ws(const char* p) { |
302 | 77.7M | while (is_ws(*p)) ++p; |
303 | 77.1M | return p; |
304 | 77.1M | } |
305 | | |
306 | 1.82M | static inline float pow10(int32_t exp) { |
307 | 1.82M | static constexpr float g_pow10_table[63] = |
308 | 1.82M | { |
309 | 1.82M | 1.e-031f, 1.e-030f, 1.e-029f, 1.e-028f, 1.e-027f, 1.e-026f, 1.e-025f, 1.e-024f, |
310 | 1.82M | 1.e-023f, 1.e-022f, 1.e-021f, 1.e-020f, 1.e-019f, 1.e-018f, 1.e-017f, 1.e-016f, |
311 | 1.82M | 1.e-015f, 1.e-014f, 1.e-013f, 1.e-012f, 1.e-011f, 1.e-010f, 1.e-009f, 1.e-008f, |
312 | 1.82M | 1.e-007f, 1.e-006f, 1.e-005f, 1.e-004f, 1.e-003f, 1.e-002f, 1.e-001f, 1.e+000f, |
313 | 1.82M | 1.e+001f, 1.e+002f, 1.e+003f, 1.e+004f, 1.e+005f, 1.e+006f, 1.e+007f, 1.e+008f, |
314 | 1.82M | 1.e+009f, 1.e+010f, 1.e+011f, 1.e+012f, 1.e+013f, 1.e+014f, 1.e+015f, 1.e+016f, |
315 | 1.82M | 1.e+017f, 1.e+018f, 1.e+019f, 1.e+020f, 1.e+021f, 1.e+022f, 1.e+023f, 1.e+024f, |
316 | 1.82M | 1.e+025f, 1.e+026f, 1.e+027f, 1.e+028f, 1.e+029f, 1.e+030f, 1.e+031f |
317 | 1.82M | }; |
318 | | |
319 | 1.82M | static constexpr int32_t k_exp_offset = std::size(g_pow10_table) / 2; |
320 | | |
321 | | // We only support negative exponents for now. |
322 | 1.82M | SkASSERT(exp <= 0); |
323 | | |
324 | 1.82M | return (exp >= -k_exp_offset) ? g_pow10_table[exp + k_exp_offset] |
325 | 1.82M | : std::pow(10.0f, static_cast<float>(exp)); |
326 | 1.82M | } |
327 | | |
328 | | class DOMParser { |
329 | | public: |
330 | | explicit DOMParser(SkArenaAlloc& alloc) |
331 | 12.0k | : fAlloc(alloc) { |
332 | 12.0k | fValueStack.reserve(kValueStackReserve); |
333 | 12.0k | fUnescapeBuffer.reserve(kUnescapeBufferReserve); |
334 | 12.0k | } |
335 | | |
336 | 12.0k | Value parse(const char* p, size_t size) { |
337 | 12.0k | if (!size) { |
338 | 0 | return this->error(NullValue(), p, "invalid empty input"); |
339 | 0 | } |
340 | | |
341 | 12.0k | const char* p_stop = p + size - 1; |
342 | | |
343 | | // We're only checking for end-of-stream on object/array close('}',']'), |
344 | | // so we must trim any whitespace from the buffer tail. |
345 | 12.4k | while (p_stop > p && is_ws(*p_stop)) --p_stop; |
346 | | |
347 | 12.0k | SkASSERT(p_stop >= p && p_stop < p + size); |
348 | 12.0k | if (!is_eoscope(*p_stop)) { |
349 | 30 | return this->error(NullValue(), p_stop, "invalid top-level value"); |
350 | 30 | } |
351 | | |
352 | 11.9k | p = skip_ws(p); |
353 | | |
354 | 11.9k | switch (*p) { |
355 | 9.86k | case '{': |
356 | 9.86k | goto match_object; |
357 | 2.10k | case '[': |
358 | 2.10k | goto match_array; |
359 | 17 | default: |
360 | 17 | return this->error(NullValue(), p, "invalid top-level value"); |
361 | 11.9k | } |
362 | | |
363 | 1.20M | match_object: |
364 | 1.20M | SkASSERT(*p == '{'); |
365 | 1.20M | p = skip_ws(p + 1); |
366 | | |
367 | 1.20M | this->pushObjectScope(); |
368 | | |
369 | 1.20M | if (*p == '}') goto pop_object; |
370 | | |
371 | | // goto match_object_key; |
372 | 5.26M | match_object_key: |
373 | 5.26M | p = skip_ws(p); |
374 | 5.26M | if (*p != '"') return this->error(NullValue(), p, "expected object key"); |
375 | | |
376 | 5.26M | p = this->matchString(p, p_stop, [this](const char* key, size_t size, const char* eos) { |
377 | 5.26M | this->pushObjectKey(key, size, eos); |
378 | 5.26M | }); |
379 | 5.26M | if (!p) return NullValue(); |
380 | | |
381 | 5.26M | p = skip_ws(p); |
382 | 5.26M | if (*p != ':') return this->error(NullValue(), p, "expected ':' separator"); |
383 | | |
384 | 5.26M | ++p; |
385 | | |
386 | | // goto match_value; |
387 | 30.5M | match_value: |
388 | 30.5M | p = skip_ws(p); |
389 | | |
390 | 30.5M | switch (*p) { |
391 | 2 | case '\0': |
392 | 2 | return this->error(NullValue(), p, "unexpected input end"); |
393 | 1.16M | case '"': |
394 | 1.16M | p = this->matchString(p, p_stop, [this](const char* str, size_t size, const char* eos) { |
395 | 1.16M | this->pushString(str, size, eos); |
396 | 1.16M | }); |
397 | 1.16M | break; |
398 | 15.9M | case '[': |
399 | 15.9M | goto match_array; |
400 | 53.6k | case 'f': |
401 | 53.6k | p = this->matchFalse(p); |
402 | 53.6k | break; |
403 | 1.68k | case 'n': |
404 | 1.68k | p = this->matchNull(p); |
405 | 1.68k | break; |
406 | 40.6k | case 't': |
407 | 40.6k | p = this->matchTrue(p); |
408 | 40.6k | break; |
409 | 1.19M | case '{': |
410 | 1.19M | goto match_object; |
411 | 12.0M | default: |
412 | 12.0M | p = this->matchNumber(p); |
413 | 12.0M | break; |
414 | 30.5M | } |
415 | | |
416 | 13.3M | if (!p) return NullValue(); |
417 | | |
418 | | // goto match_post_value; |
419 | 18.8M | match_post_value: |
420 | 18.8M | SkASSERT(!this->inTopLevelScope()); |
421 | | |
422 | 18.8M | p = skip_ws(p); |
423 | 18.8M | switch (*p) { |
424 | 13.3M | case ',': |
425 | 13.3M | ++p; |
426 | 13.3M | if (this->inObjectScope()) { |
427 | 4.09M | goto match_object_key; |
428 | 9.29M | } else { |
429 | 9.29M | SkASSERT(this->inArrayScope()); |
430 | 9.29M | goto match_value; |
431 | 9.29M | } |
432 | 4.28M | case ']': |
433 | 4.28M | goto pop_array; |
434 | 1.15M | case '}': |
435 | 1.15M | goto pop_object; |
436 | 89 | default: |
437 | 89 | return this->error(NullValue(), p - 1, "unexpected value-trailing token"); |
438 | 18.8M | } |
439 | | |
440 | | // unreachable |
441 | 0 | SkASSERT(false); |
442 | |
|
443 | 1.18M | pop_object: |
444 | 1.18M | SkASSERT(*p == '}'); |
445 | | |
446 | 1.18M | if (this->inArrayScope()) { |
447 | 283 | return this->error(NullValue(), p, "unexpected object terminator"); |
448 | 283 | } |
449 | | |
450 | 1.18M | this->popObjectScope(); |
451 | | |
452 | | // goto pop_common |
453 | 5.48M | pop_common: |
454 | 5.48M | SkASSERT(is_eoscope(*p)); |
455 | | |
456 | 5.48M | if (this->inTopLevelScope()) { |
457 | 10.4k | SkASSERT(fValueStack.size() == 1); |
458 | | |
459 | | // Success condition: parsed the top level element and reached the stop token. |
460 | 10.4k | return p == p_stop |
461 | 10.4k | ? fValueStack.front() |
462 | 10.4k | : this->error(NullValue(), p + 1, "trailing root garbage"); |
463 | 10.4k | } |
464 | | |
465 | 5.47M | if (p == p_stop) { |
466 | 166 | return this->error(NullValue(), p, "unexpected end-of-input"); |
467 | 166 | } |
468 | | |
469 | 5.47M | ++p; |
470 | | |
471 | 5.47M | goto match_post_value; |
472 | | |
473 | 15.9M | match_array: |
474 | 15.9M | SkASSERT(*p == '['); |
475 | 15.9M | p = skip_ws(p + 1); |
476 | | |
477 | 15.9M | this->pushArrayScope(); |
478 | | |
479 | 15.9M | if (*p != ']') goto match_value; |
480 | | |
481 | | // goto pop_array; |
482 | 4.30M | pop_array: |
483 | 4.30M | SkASSERT(*p == ']'); |
484 | | |
485 | 4.30M | if (this->inObjectScope()) { |
486 | 43 | return this->error(NullValue(), p, "unexpected array terminator"); |
487 | 43 | } |
488 | | |
489 | 4.30M | this->popArrayScope(); |
490 | | |
491 | 4.30M | goto pop_common; |
492 | | |
493 | 0 | SkASSERT(false); |
494 | 0 | return NullValue(); |
495 | 4.30M | } SkJSON.cpp:skjson::(anonymous namespace)::DOMParser::parse(char const*, unsigned long) Line | Count | Source | 336 | 12.0k | Value parse(const char* p, size_t size) { | 337 | 12.0k | if (!size) { | 338 | 0 | return this->error(NullValue(), p, "invalid empty input"); | 339 | 0 | } | 340 | | | 341 | 12.0k | const char* p_stop = p + size - 1; | 342 | | | 343 | | // We're only checking for end-of-stream on object/array close('}',']'), | 344 | | // so we must trim any whitespace from the buffer tail. | 345 | 12.4k | while (p_stop > p && is_ws(*p_stop)) --p_stop; | 346 | | | 347 | 12.0k | SkASSERT(p_stop >= p && p_stop < p + size); | 348 | 12.0k | if (!is_eoscope(*p_stop)) { | 349 | 30 | return this->error(NullValue(), p_stop, "invalid top-level value"); | 350 | 30 | } | 351 | | | 352 | 11.9k | p = skip_ws(p); | 353 | | | 354 | 11.9k | switch (*p) { | 355 | 9.86k | case '{': | 356 | 9.86k | goto match_object; | 357 | 2.10k | case '[': | 358 | 2.10k | goto match_array; | 359 | 17 | default: | 360 | 17 | return this->error(NullValue(), p, "invalid top-level value"); | 361 | 11.9k | } | 362 | | | 363 | 1.20M | match_object: | 364 | 1.20M | SkASSERT(*p == '{'); | 365 | 1.20M | p = skip_ws(p + 1); | 366 | | | 367 | 1.20M | this->pushObjectScope(); | 368 | | | 369 | 1.20M | if (*p == '}') goto pop_object; | 370 | | | 371 | | // goto match_object_key; | 372 | 5.26M | match_object_key: | 373 | 5.26M | p = skip_ws(p); | 374 | 5.26M | if (*p != '"') return this->error(NullValue(), p, "expected object key"); | 375 | | | 376 | 5.26M | p = this->matchString(p, p_stop, [this](const char* key, size_t size, const char* eos) { | 377 | 5.26M | this->pushObjectKey(key, size, eos); | 378 | 5.26M | }); | 379 | 5.26M | if (!p) return NullValue(); | 380 | | | 381 | 5.26M | p = skip_ws(p); | 382 | 5.26M | if (*p != ':') return this->error(NullValue(), p, "expected ':' separator"); | 383 | | | 384 | 5.26M | ++p; | 385 | | | 386 | | // goto match_value; | 387 | 30.5M | match_value: | 388 | 30.5M | p = skip_ws(p); | 389 | | | 390 | 30.5M | switch (*p) { | 391 | 2 | case '\0': | 392 | 2 | return this->error(NullValue(), p, "unexpected input end"); | 393 | 1.16M | case '"': | 394 | 1.16M | p = this->matchString(p, p_stop, [this](const char* str, size_t size, const char* eos) { | 395 | 1.16M | this->pushString(str, size, eos); | 396 | 1.16M | }); | 397 | 1.16M | break; | 398 | 15.9M | case '[': | 399 | 15.9M | goto match_array; | 400 | 53.6k | case 'f': | 401 | 53.6k | p = this->matchFalse(p); | 402 | 53.6k | break; | 403 | 1.68k | case 'n': | 404 | 1.68k | p = this->matchNull(p); | 405 | 1.68k | break; | 406 | 40.6k | case 't': | 407 | 40.6k | p = this->matchTrue(p); | 408 | 40.6k | break; | 409 | 1.19M | case '{': | 410 | 1.19M | goto match_object; | 411 | 12.0M | default: | 412 | 12.0M | p = this->matchNumber(p); | 413 | 12.0M | break; | 414 | 30.5M | } | 415 | | | 416 | 13.3M | if (!p) return NullValue(); | 417 | | | 418 | | // goto match_post_value; | 419 | 18.8M | match_post_value: | 420 | 18.8M | SkASSERT(!this->inTopLevelScope()); | 421 | | | 422 | 18.8M | p = skip_ws(p); | 423 | 18.8M | switch (*p) { | 424 | 13.3M | case ',': | 425 | 13.3M | ++p; | 426 | 13.3M | if (this->inObjectScope()) { | 427 | 4.09M | goto match_object_key; | 428 | 9.29M | } else { | 429 | 9.29M | SkASSERT(this->inArrayScope()); | 430 | 9.29M | goto match_value; | 431 | 9.29M | } | 432 | 4.28M | case ']': | 433 | 4.28M | goto pop_array; | 434 | 1.15M | case '}': | 435 | 1.15M | goto pop_object; | 436 | 89 | default: | 437 | 89 | return this->error(NullValue(), p - 1, "unexpected value-trailing token"); | 438 | 18.8M | } | 439 | | | 440 | | // unreachable | 441 | 0 | SkASSERT(false); | 442 | |
| 443 | 1.18M | pop_object: | 444 | 1.18M | SkASSERT(*p == '}'); | 445 | | | 446 | 1.18M | if (this->inArrayScope()) { | 447 | 283 | return this->error(NullValue(), p, "unexpected object terminator"); | 448 | 283 | } | 449 | | | 450 | 1.18M | this->popObjectScope(); | 451 | | | 452 | | // goto pop_common | 453 | 5.48M | pop_common: | 454 | 5.48M | SkASSERT(is_eoscope(*p)); | 455 | | | 456 | 5.48M | if (this->inTopLevelScope()) { | 457 | 10.4k | SkASSERT(fValueStack.size() == 1); | 458 | | | 459 | | // Success condition: parsed the top level element and reached the stop token. | 460 | 10.4k | return p == p_stop | 461 | 10.4k | ? fValueStack.front() | 462 | 10.4k | : this->error(NullValue(), p + 1, "trailing root garbage"); | 463 | 10.4k | } | 464 | | | 465 | 5.47M | if (p == p_stop) { | 466 | 166 | return this->error(NullValue(), p, "unexpected end-of-input"); | 467 | 166 | } | 468 | | | 469 | 5.47M | ++p; | 470 | | | 471 | 5.47M | goto match_post_value; | 472 | | | 473 | 15.9M | match_array: | 474 | 15.9M | SkASSERT(*p == '['); | 475 | 15.9M | p = skip_ws(p + 1); | 476 | | | 477 | 15.9M | this->pushArrayScope(); | 478 | | | 479 | 15.9M | if (*p != ']') goto match_value; | 480 | | | 481 | | // goto pop_array; | 482 | 4.30M | pop_array: | 483 | 4.30M | SkASSERT(*p == ']'); | 484 | | | 485 | 4.30M | if (this->inObjectScope()) { | 486 | 43 | return this->error(NullValue(), p, "unexpected array terminator"); | 487 | 43 | } | 488 | | | 489 | 4.30M | this->popArrayScope(); | 490 | | | 491 | 4.30M | goto pop_common; | 492 | | | 493 | 0 | SkASSERT(false); | 494 | 0 | return NullValue(); | 495 | 4.30M | } |
Unexecuted instantiation: SkJSON.cpp:skjson::(anonymous namespace)::DOMParser::parse(char const*, unsigned long) |
496 | | |
497 | 0 | std::tuple<const char*, const SkString> getError() const { |
498 | 0 | return std::make_tuple(fErrorToken, fErrorMessage); |
499 | 0 | } |
500 | | |
501 | | private: |
502 | | SkArenaAlloc& fAlloc; |
503 | | |
504 | | // Pending values stack. |
505 | | inline static constexpr size_t kValueStackReserve = 256; |
506 | | std::vector<Value> fValueStack; |
507 | | |
508 | | // String unescape buffer. |
509 | | inline static constexpr size_t kUnescapeBufferReserve = 512; |
510 | | std::vector<char> fUnescapeBuffer; |
511 | | |
512 | | // Tracks the current object/array scope, as an index into fStack: |
513 | | // |
514 | | // - for objects: fScopeIndex = (index of first value in scope) |
515 | | // - for arrays : fScopeIndex = -(index of first value in scope) |
516 | | // |
517 | | // fScopeIndex == 0 IFF we are at the top level (no current/active scope). |
518 | | intptr_t fScopeIndex = 0; |
519 | | |
520 | | // Error reporting. |
521 | | const char* fErrorToken = nullptr; |
522 | | SkString fErrorMessage; |
523 | | |
524 | 5.48M | bool inTopLevelScope() const { return fScopeIndex == 0; } |
525 | 17.6M | bool inObjectScope() const { return fScopeIndex > 0; } |
526 | 1.18M | bool inArrayScope() const { return fScopeIndex < 0; } |
527 | | |
528 | | // Helper for masquerading raw primitive types as Values (bypassing tagging, etc). |
529 | | template <typename T> |
530 | | class RawValue final : public Value { |
531 | | public: |
532 | 17.1M | explicit RawValue(T v) { |
533 | 17.1M | static_assert(sizeof(T) <= sizeof(Value), ""); |
534 | 17.1M | *this->cast<T>() = v; |
535 | 17.1M | } |
536 | | |
537 | 5.48M | T operator *() const { return *this->cast<T>(); } |
538 | | }; |
539 | | |
540 | | template <typename VectorT> |
541 | 5.48M | void popScopeAsVec(size_t scope_start) { |
542 | 5.48M | SkASSERT(scope_start > 0); |
543 | 5.48M | SkASSERT(scope_start <= fValueStack.size()); |
544 | | |
545 | 5.48M | using T = typename VectorT::ValueT; |
546 | 5.48M | static_assert( sizeof(T) >= sizeof(Value), ""); |
547 | 5.48M | static_assert( sizeof(T) % sizeof(Value) == 0, ""); |
548 | 5.48M | static_assert(alignof(T) == alignof(Value), ""); |
549 | | |
550 | 5.48M | const auto scope_count = fValueStack.size() - scope_start, |
551 | 5.48M | count = scope_count / (sizeof(T) / sizeof(Value)); |
552 | 5.48M | SkASSERT(scope_count % (sizeof(T) / sizeof(Value)) == 0); |
553 | | |
554 | 5.48M | const auto* begin = reinterpret_cast<const T*>(fValueStack.data() + scope_start); |
555 | | |
556 | | // Restore the previous scope index from saved placeholder value, |
557 | | // and instantiate as a vector of values in scope. |
558 | 5.48M | auto& placeholder = fValueStack[scope_start - 1]; |
559 | 5.48M | fScopeIndex = *static_cast<RawValue<intptr_t>&>(placeholder); |
560 | 5.48M | placeholder = VectorT(begin, count, fAlloc); |
561 | | |
562 | | // Drop the (consumed) values in scope. |
563 | 5.48M | fValueStack.resize(scope_start); |
564 | 5.48M | } SkJSON.cpp:void skjson::(anonymous namespace)::DOMParser::popScopeAsVec<skjson::ObjectValue>(unsigned long) Line | Count | Source | 541 | 1.18M | void popScopeAsVec(size_t scope_start) { | 542 | 1.18M | SkASSERT(scope_start > 0); | 543 | 1.18M | SkASSERT(scope_start <= fValueStack.size()); | 544 | | | 545 | 1.18M | using T = typename VectorT::ValueT; | 546 | 1.18M | static_assert( sizeof(T) >= sizeof(Value), ""); | 547 | 1.18M | static_assert( sizeof(T) % sizeof(Value) == 0, ""); | 548 | 1.18M | static_assert(alignof(T) == alignof(Value), ""); | 549 | | | 550 | 1.18M | const auto scope_count = fValueStack.size() - scope_start, | 551 | 1.18M | count = scope_count / (sizeof(T) / sizeof(Value)); | 552 | 1.18M | SkASSERT(scope_count % (sizeof(T) / sizeof(Value)) == 0); | 553 | | | 554 | 1.18M | const auto* begin = reinterpret_cast<const T*>(fValueStack.data() + scope_start); | 555 | | | 556 | | // Restore the previous scope index from saved placeholder value, | 557 | | // and instantiate as a vector of values in scope. | 558 | 1.18M | auto& placeholder = fValueStack[scope_start - 1]; | 559 | 1.18M | fScopeIndex = *static_cast<RawValue<intptr_t>&>(placeholder); | 560 | 1.18M | placeholder = VectorT(begin, count, fAlloc); | 561 | | | 562 | | // Drop the (consumed) values in scope. | 563 | 1.18M | fValueStack.resize(scope_start); | 564 | 1.18M | } |
SkJSON.cpp:void skjson::(anonymous namespace)::DOMParser::popScopeAsVec<skjson::ArrayValue>(unsigned long) Line | Count | Source | 541 | 4.30M | void popScopeAsVec(size_t scope_start) { | 542 | 4.30M | SkASSERT(scope_start > 0); | 543 | 4.30M | SkASSERT(scope_start <= fValueStack.size()); | 544 | | | 545 | 4.30M | using T = typename VectorT::ValueT; | 546 | 4.30M | static_assert( sizeof(T) >= sizeof(Value), ""); | 547 | 4.30M | static_assert( sizeof(T) % sizeof(Value) == 0, ""); | 548 | 4.30M | static_assert(alignof(T) == alignof(Value), ""); | 549 | | | 550 | 4.30M | const auto scope_count = fValueStack.size() - scope_start, | 551 | 4.30M | count = scope_count / (sizeof(T) / sizeof(Value)); | 552 | 4.30M | SkASSERT(scope_count % (sizeof(T) / sizeof(Value)) == 0); | 553 | | | 554 | 4.30M | const auto* begin = reinterpret_cast<const T*>(fValueStack.data() + scope_start); | 555 | | | 556 | | // Restore the previous scope index from saved placeholder value, | 557 | | // and instantiate as a vector of values in scope. | 558 | 4.30M | auto& placeholder = fValueStack[scope_start - 1]; | 559 | 4.30M | fScopeIndex = *static_cast<RawValue<intptr_t>&>(placeholder); | 560 | 4.30M | placeholder = VectorT(begin, count, fAlloc); | 561 | | | 562 | | // Drop the (consumed) values in scope. | 563 | 4.30M | fValueStack.resize(scope_start); | 564 | 4.30M | } |
|
565 | | |
566 | 1.20M | void pushObjectScope() { |
567 | | // Save a scope index now, and then later we'll overwrite this value as the Object itself. |
568 | 1.20M | fValueStack.push_back(RawValue<intptr_t>(fScopeIndex)); |
569 | | |
570 | | // New object scope. |
571 | 1.20M | fScopeIndex = SkTo<intptr_t>(fValueStack.size()); |
572 | 1.20M | } |
573 | | |
574 | 1.18M | void popObjectScope() { |
575 | 1.18M | SkASSERT(this->inObjectScope()); |
576 | 1.18M | this->popScopeAsVec<ObjectValue>(SkTo<size_t>(fScopeIndex)); |
577 | | |
578 | 1.18M | SkDEBUGCODE( |
579 | 1.18M | const auto& obj = fValueStack.back().as<ObjectValue>(); |
580 | 1.18M | SkASSERT(obj.is<ObjectValue>()); |
581 | 1.18M | for (const auto& member : obj) { |
582 | 1.18M | SkASSERT(member.fKey.is<StringValue>()); |
583 | 1.18M | } |
584 | 1.18M | ) |
585 | 1.18M | } |
586 | | |
587 | 15.9M | void pushArrayScope() { |
588 | | // Save a scope index now, and then later we'll overwrite this value as the Array itself. |
589 | 15.9M | fValueStack.push_back(RawValue<intptr_t>(fScopeIndex)); |
590 | | |
591 | | // New array scope. |
592 | 15.9M | fScopeIndex = -SkTo<intptr_t>(fValueStack.size()); |
593 | 15.9M | } |
594 | | |
595 | 4.30M | void popArrayScope() { |
596 | 4.30M | SkASSERT(this->inArrayScope()); |
597 | 4.30M | this->popScopeAsVec<ArrayValue>(SkTo<size_t>(-fScopeIndex)); |
598 | | |
599 | 4.30M | SkDEBUGCODE( |
600 | 4.30M | const auto& arr = fValueStack.back().as<ArrayValue>(); |
601 | 4.30M | SkASSERT(arr.is<ArrayValue>()); |
602 | 4.30M | ) |
603 | 4.30M | } |
604 | | |
605 | 5.26M | void pushObjectKey(const char* key, size_t size, const char* eos) { |
606 | 5.26M | SkASSERT(this->inObjectScope()); |
607 | 5.26M | SkASSERT(fValueStack.size() >= SkTo<size_t>(fScopeIndex)); |
608 | 5.26M | SkASSERT(!((fValueStack.size() - SkTo<size_t>(fScopeIndex)) & 1)); |
609 | 5.26M | this->pushString(key, size, eos); |
610 | 5.26M | } |
611 | | |
612 | 40.6k | void pushTrue() { |
613 | 40.6k | fValueStack.push_back(BoolValue(true)); |
614 | 40.6k | } |
615 | | |
616 | 53.5k | void pushFalse() { |
617 | 53.5k | fValueStack.push_back(BoolValue(false)); |
618 | 53.5k | } |
619 | | |
620 | 1.63k | void pushNull() { |
621 | 1.63k | fValueStack.push_back(NullValue()); |
622 | 1.63k | } |
623 | | |
624 | 6.42M | void pushString(const char* s, size_t size, const char* eos) { |
625 | 6.42M | fValueStack.push_back(FastString(s, size, eos, fAlloc)); |
626 | 6.42M | } |
627 | | |
628 | 10.1M | void pushInt32(int32_t i) { |
629 | 10.1M | fValueStack.push_back(NumberValue(i)); |
630 | 10.1M | } |
631 | | |
632 | 1.96M | void pushFloat(float f) { |
633 | 1.96M | fValueStack.push_back(NumberValue(f)); |
634 | 1.96M | } |
635 | | |
636 | | template <typename T> |
637 | 1.60k | T error(T&& ret_val, const char* p, const char* msg) { |
638 | | #if defined(SK_JSON_REPORT_ERRORS) |
639 | | fErrorToken = p; |
640 | | fErrorMessage.set(msg); |
641 | | #endif |
642 | 1.60k | return ret_val; |
643 | 1.60k | } SkJSON.cpp:skjson::NullValue skjson::(anonymous namespace)::DOMParser::error<skjson::NullValue>(skjson::NullValue&&, char const*, char const*) Line | Count | Source | 637 | 1.05k | T error(T&& ret_val, const char* p, const char* msg) { | 638 | | #if defined(SK_JSON_REPORT_ERRORS) | 639 | | fErrorToken = p; | 640 | | fErrorMessage.set(msg); | 641 | | #endif | 642 | 1.05k | return ret_val; | 643 | 1.05k | } |
SkJSON.cpp:decltype(nullptr) skjson::(anonymous namespace)::DOMParser::error<decltype(nullptr)>(decltype(nullptr)&&, char const*, char const*) Line | Count | Source | 637 | 559 | T error(T&& ret_val, const char* p, const char* msg) { | 638 | | #if defined(SK_JSON_REPORT_ERRORS) | 639 | | fErrorToken = p; | 640 | | fErrorMessage.set(msg); | 641 | | #endif | 642 | 559 | return ret_val; | 643 | 559 | } |
|
644 | | |
645 | 40.6k | const char* matchTrue(const char* p) { |
646 | 40.6k | SkASSERT(p[0] == 't'); |
647 | | |
648 | 40.6k | if (p[1] == 'r' && p[2] == 'u' && p[3] == 'e') { |
649 | 40.6k | this->pushTrue(); |
650 | 40.6k | return p + 4; |
651 | 40.6k | } |
652 | | |
653 | 50 | return this->error(nullptr, p, "invalid token"); |
654 | 40.6k | } SkJSON.cpp:skjson::(anonymous namespace)::DOMParser::matchTrue(char const*) Line | Count | Source | 645 | 40.6k | const char* matchTrue(const char* p) { | 646 | 40.6k | SkASSERT(p[0] == 't'); | 647 | | | 648 | 40.6k | if (p[1] == 'r' && p[2] == 'u' && p[3] == 'e') { | 649 | 40.6k | this->pushTrue(); | 650 | 40.6k | return p + 4; | 651 | 40.6k | } | 652 | | | 653 | 50 | return this->error(nullptr, p, "invalid token"); | 654 | 40.6k | } |
Unexecuted instantiation: SkJSON.cpp:skjson::(anonymous namespace)::DOMParser::matchTrue(char const*) |
655 | | |
656 | 53.6k | const char* matchFalse(const char* p) { |
657 | 53.6k | SkASSERT(p[0] == 'f'); |
658 | | |
659 | 53.6k | if (p[1] == 'a' && p[2] == 'l' && p[3] == 's' && p[4] == 'e') { |
660 | 53.5k | this->pushFalse(); |
661 | 53.5k | return p + 5; |
662 | 53.5k | } |
663 | | |
664 | 61 | return this->error(nullptr, p, "invalid token"); |
665 | 53.6k | } SkJSON.cpp:skjson::(anonymous namespace)::DOMParser::matchFalse(char const*) Line | Count | Source | 656 | 53.6k | const char* matchFalse(const char* p) { | 657 | 53.6k | SkASSERT(p[0] == 'f'); | 658 | | | 659 | 53.6k | if (p[1] == 'a' && p[2] == 'l' && p[3] == 's' && p[4] == 'e') { | 660 | 53.5k | this->pushFalse(); | 661 | 53.5k | return p + 5; | 662 | 53.5k | } | 663 | | | 664 | 61 | return this->error(nullptr, p, "invalid token"); | 665 | 53.6k | } |
Unexecuted instantiation: SkJSON.cpp:skjson::(anonymous namespace)::DOMParser::matchFalse(char const*) |
666 | | |
667 | 1.68k | const char* matchNull(const char* p) { |
668 | 1.68k | SkASSERT(p[0] == 'n'); |
669 | | |
670 | 1.68k | if (p[1] == 'u' && p[2] == 'l' && p[3] == 'l') { |
671 | 1.63k | this->pushNull(); |
672 | 1.63k | return p + 4; |
673 | 1.63k | } |
674 | | |
675 | 52 | return this->error(nullptr, p, "invalid token"); |
676 | 1.68k | } SkJSON.cpp:skjson::(anonymous namespace)::DOMParser::matchNull(char const*) Line | Count | Source | 667 | 1.68k | const char* matchNull(const char* p) { | 668 | 1.68k | SkASSERT(p[0] == 'n'); | 669 | | | 670 | 1.68k | if (p[1] == 'u' && p[2] == 'l' && p[3] == 'l') { | 671 | 1.63k | this->pushNull(); | 672 | 1.63k | return p + 4; | 673 | 1.63k | } | 674 | | | 675 | 52 | return this->error(nullptr, p, "invalid token"); | 676 | 1.68k | } |
Unexecuted instantiation: SkJSON.cpp:skjson::(anonymous namespace)::DOMParser::matchNull(char const*) |
677 | | |
678 | 55.2k | const std::vector<char>* unescapeString(const char* begin, const char* end) { |
679 | 55.2k | fUnescapeBuffer.clear(); |
680 | | |
681 | 32.5M | for (const auto* p = begin; p != end; ++p) { |
682 | 32.4M | if (*p != '\\') { |
683 | 32.1M | fUnescapeBuffer.push_back(*p); |
684 | 32.1M | continue; |
685 | 32.1M | } |
686 | | |
687 | 323k | if (++p == end) { |
688 | 0 | return nullptr; |
689 | 0 | } |
690 | | |
691 | 323k | switch (*p) { |
692 | 64.9k | case '"': fUnescapeBuffer.push_back( '"'); break; |
693 | 80.8k | case '\\': fUnescapeBuffer.push_back('\\'); break; |
694 | 13.8k | case '/': fUnescapeBuffer.push_back( '/'); break; |
695 | 13.1k | case 'b': fUnescapeBuffer.push_back('\b'); break; |
696 | 11.4k | case 'f': fUnescapeBuffer.push_back('\f'); break; |
697 | 40.8k | case 'n': fUnescapeBuffer.push_back('\n'); break; |
698 | 15.9k | case 'r': fUnescapeBuffer.push_back('\r'); break; |
699 | 19.5k | case 't': fUnescapeBuffer.push_back('\t'); break; |
700 | 62.6k | case 'u': { |
701 | 62.6k | if (p + 4 >= end) { |
702 | 4 | return nullptr; |
703 | 4 | } |
704 | | |
705 | 62.6k | uint32_t hexed; |
706 | 62.6k | const char hex_str[] = {p[1], p[2], p[3], p[4], '\0'}; |
707 | 62.6k | const auto* eos = SkParse::FindHex(hex_str, &hexed); |
708 | 62.6k | if (!eos || *eos) { |
709 | 58 | return nullptr; |
710 | 58 | } |
711 | | |
712 | 62.5k | char utf8[SkUTF::kMaxBytesInUTF8Sequence]; |
713 | 62.5k | const auto utf8_len = SkUTF::ToUTF8(SkTo<SkUnichar>(hexed), utf8); |
714 | 62.5k | fUnescapeBuffer.insert(fUnescapeBuffer.end(), utf8, utf8 + utf8_len); |
715 | 62.5k | p += 4; |
716 | 62.5k | } break; |
717 | 27 | default: return nullptr; |
718 | 323k | } |
719 | 323k | } |
720 | | |
721 | 55.1k | return &fUnescapeBuffer; |
722 | 55.2k | } |
723 | | |
724 | | template <typename MatchFunc> |
725 | 6.43M | const char* matchString(const char* p, const char* p_stop, MatchFunc&& func) { |
726 | 6.43M | SkASSERT(*p == '"'); |
727 | 6.43M | const auto* s_begin = p + 1; |
728 | 6.43M | bool requires_unescape = false; |
729 | | |
730 | 8.39M | do { |
731 | | // Consume string chars. |
732 | | // This is the fast path, and hopefully we only hit it once then quick-exit below. |
733 | 138M | for (p = p + 1; !is_eostring(*p); ++p); |
734 | | |
735 | 8.39M | if (*p == '"') { |
736 | | // Valid string found. |
737 | 6.42M | if (!requires_unescape) { |
738 | 6.37M | func(s_begin, p - s_begin, p_stop); |
739 | 6.37M | } else { |
740 | | // Slow unescape. We could avoid this extra copy with some effort, |
741 | | // but in practice escaped strings should be rare. |
742 | 55.2k | const auto* buf = this->unescapeString(s_begin, p); |
743 | 55.2k | if (!buf) { |
744 | 89 | break; |
745 | 89 | } |
746 | | |
747 | 55.1k | SkASSERT(!buf->empty()); |
748 | 55.1k | func(buf->data(), buf->size(), buf->data() + buf->size() - 1); |
749 | 55.1k | } |
750 | 6.42M | return p + 1; |
751 | 6.42M | } |
752 | | |
753 | 1.96M | if (*p == '\\') { |
754 | 330k | requires_unescape = true; |
755 | 330k | ++p; |
756 | 330k | continue; |
757 | 330k | } |
758 | | |
759 | | // End-of-scope chars are special: we use them to tag the end of the input. |
760 | | // Thus they cannot be consumed indiscriminately -- we need to check if we hit the |
761 | | // end of the input. To that effect, we treat them as string terminators above, |
762 | | // then we catch them here. |
763 | 1.63M | if (is_eoscope(*p)) { |
764 | 1.63M | continue; |
765 | 1.63M | } |
766 | | |
767 | | // Invalid/unexpected char. |
768 | 29 | break; |
769 | 1.96M | } while (p != p_stop); |
770 | | |
771 | | // Premature end-of-input, or illegal string char. |
772 | 222 | return this->error(nullptr, s_begin - 1, "invalid string"); |
773 | 6.43M | } SkJSON.cpp:char const* skjson::(anonymous namespace)::DOMParser::matchString<skjson::(anonymous namespace)::DOMParser::parse(char const*, unsigned long)::{lambda(char const*, unsigned long, char const*)#1}>(char const*, char const*, skjson::(anonymous namespace)::DOMParser::parse(char const*, unsigned long)::{lambda(char const*, unsigned long, char const*)#1}&&) Line | Count | Source | 725 | 5.26M | const char* matchString(const char* p, const char* p_stop, MatchFunc&& func) { | 726 | 5.26M | SkASSERT(*p == '"'); | 727 | 5.26M | const auto* s_begin = p + 1; | 728 | 5.26M | bool requires_unescape = false; | 729 | | | 730 | 6.31M | do { | 731 | | // Consume string chars. | 732 | | // This is the fast path, and hopefully we only hit it once then quick-exit below. | 733 | 87.8M | for (p = p + 1; !is_eostring(*p); ++p); | 734 | | | 735 | 6.31M | if (*p == '"') { | 736 | | // Valid string found. | 737 | 5.26M | if (!requires_unescape) { | 738 | 5.23M | func(s_begin, p - s_begin, p_stop); | 739 | 5.23M | } else { | 740 | | // Slow unescape. We could avoid this extra copy with some effort, | 741 | | // but in practice escaped strings should be rare. | 742 | 38.9k | const auto* buf = this->unescapeString(s_begin, p); | 743 | 38.9k | if (!buf) { | 744 | 70 | break; | 745 | 70 | } | 746 | | | 747 | 38.9k | SkASSERT(!buf->empty()); | 748 | 38.9k | func(buf->data(), buf->size(), buf->data() + buf->size() - 1); | 749 | 38.9k | } | 750 | 5.26M | return p + 1; | 751 | 5.26M | } | 752 | | | 753 | 1.04M | if (*p == '\\') { | 754 | 194k | requires_unescape = true; | 755 | 194k | ++p; | 756 | 194k | continue; | 757 | 194k | } | 758 | | | 759 | | // End-of-scope chars are special: we use them to tag the end of the input. | 760 | | // Thus they cannot be consumed indiscriminately -- we need to check if we hit the | 761 | | // end of the input. To that effect, we treat them as string terminators above, | 762 | | // then we catch them here. | 763 | 846k | if (is_eoscope(*p)) { | 764 | 846k | continue; | 765 | 846k | } | 766 | | | 767 | | // Invalid/unexpected char. | 768 | 16 | break; | 769 | 1.04M | } while (p != p_stop); | 770 | | | 771 | | // Premature end-of-input, or illegal string char. | 772 | 145 | return this->error(nullptr, s_begin - 1, "invalid string"); | 773 | 5.26M | } |
SkJSON.cpp:char const* skjson::(anonymous namespace)::DOMParser::matchString<skjson::(anonymous namespace)::DOMParser::parse(char const*, unsigned long)::{lambda(char const*, unsigned long, char const*)#2}>(char const*, char const*, skjson::(anonymous namespace)::DOMParser::parse(char const*, unsigned long)::{lambda(char const*, unsigned long, char const*)#2}&&) Line | Count | Source | 725 | 1.16M | const char* matchString(const char* p, const char* p_stop, MatchFunc&& func) { | 726 | 1.16M | SkASSERT(*p == '"'); | 727 | 1.16M | const auto* s_begin = p + 1; | 728 | 1.16M | bool requires_unescape = false; | 729 | | | 730 | 2.08M | do { | 731 | | // Consume string chars. | 732 | | // This is the fast path, and hopefully we only hit it once then quick-exit below. | 733 | 50.7M | for (p = p + 1; !is_eostring(*p); ++p); | 734 | | | 735 | 2.08M | if (*p == '"') { | 736 | | // Valid string found. | 737 | 1.16M | if (!requires_unescape) { | 738 | 1.14M | func(s_begin, p - s_begin, p_stop); | 739 | 1.14M | } else { | 740 | | // Slow unescape. We could avoid this extra copy with some effort, | 741 | | // but in practice escaped strings should be rare. | 742 | 16.2k | const auto* buf = this->unescapeString(s_begin, p); | 743 | 16.2k | if (!buf) { | 744 | 19 | break; | 745 | 19 | } | 746 | | | 747 | 16.2k | SkASSERT(!buf->empty()); | 748 | 16.2k | func(buf->data(), buf->size(), buf->data() + buf->size() - 1); | 749 | 16.2k | } | 750 | 1.16M | return p + 1; | 751 | 1.16M | } | 752 | | | 753 | 926k | if (*p == '\\') { | 754 | 135k | requires_unescape = true; | 755 | 135k | ++p; | 756 | 135k | continue; | 757 | 135k | } | 758 | | | 759 | | // End-of-scope chars are special: we use them to tag the end of the input. | 760 | | // Thus they cannot be consumed indiscriminately -- we need to check if we hit the | 761 | | // end of the input. To that effect, we treat them as string terminators above, | 762 | | // then we catch them here. | 763 | 790k | if (is_eoscope(*p)) { | 764 | 790k | continue; | 765 | 790k | } | 766 | | | 767 | | // Invalid/unexpected char. | 768 | 13 | break; | 769 | 926k | } while (p != p_stop); | 770 | | | 771 | | // Premature end-of-input, or illegal string char. | 772 | 77 | return this->error(nullptr, s_begin - 1, "invalid string"); | 773 | 1.16M | } |
Unexecuted instantiation: SkJSON.cpp:char const* skjson::(anonymous namespace)::DOMParser::matchString<skjson::(anonymous namespace)::DOMParser::parse(char const*, unsigned long)::{lambda(char const*, unsigned long, char const*)#1}>(char const*, char const*, skjson::(anonymous namespace)::DOMParser::parse(char const*, unsigned long)::{lambda(char const*, unsigned long, char const*)#1}&&) Unexecuted instantiation: SkJSON.cpp:char const* skjson::(anonymous namespace)::DOMParser::matchString<skjson::(anonymous namespace)::DOMParser::parse(char const*, unsigned long)::{lambda(char const*, unsigned long, char const*)#2}>(char const*, char const*, skjson::(anonymous namespace)::DOMParser::parse(char const*, unsigned long)::{lambda(char const*, unsigned long, char const*)#2}&&) |
774 | | |
775 | 127k | const char* matchFastFloatDecimalPart(const char* p, int sign, float f, int exp) { |
776 | 127k | SkASSERT(exp <= 0); |
777 | | |
778 | 1.38M | for (;;) { |
779 | 1.38M | if (!is_digit(*p)) break; |
780 | 1.31M | f = f * 10.f + (*p++ - '0'); --exp; |
781 | 1.31M | if (!is_digit(*p)) break; |
782 | 1.25M | f = f * 10.f + (*p++ - '0'); --exp; |
783 | 1.25M | } |
784 | | |
785 | 127k | const auto decimal_scale = pow10(exp); |
786 | 127k | if (is_numeric(*p) || !decimal_scale) { |
787 | 8.70k | SkASSERT((*p == '.' || *p == 'e' || *p == 'E') || !decimal_scale); |
788 | | // Malformed input, or an (unsupported) exponent, or a collapsed decimal factor. |
789 | 8.70k | return nullptr; |
790 | 8.70k | } |
791 | | |
792 | 118k | this->pushFloat(sign * f * decimal_scale); |
793 | | |
794 | 118k | return p; |
795 | 127k | } |
796 | | |
797 | 149k | const char* matchFastFloatPart(const char* p, int sign, float f) { |
798 | 604k | for (;;) { |
799 | 604k | if (!is_digit(*p)) break; |
800 | 485k | f = f * 10.f + (*p++ - '0'); |
801 | 485k | if (!is_digit(*p)) break; |
802 | 454k | f = f * 10.f + (*p++ - '0'); |
803 | 454k | } |
804 | | |
805 | 149k | if (!is_numeric(*p)) { |
806 | | // Matched (integral) float. |
807 | 35.0k | this->pushFloat(sign * f); |
808 | 35.0k | return p; |
809 | 35.0k | } |
810 | | |
811 | 114k | return (*p == '.') ? this->matchFastFloatDecimalPart(p + 1, sign, f, 0) |
812 | 114k | : nullptr; |
813 | 149k | } |
814 | | |
815 | 12.0M | const char* matchFast32OrFloat(const char* p) { |
816 | 12.0M | int sign = 1; |
817 | 12.0M | if (*p == '-') { |
818 | 383k | sign = -1; |
819 | 383k | ++p; |
820 | 383k | } |
821 | | |
822 | 12.0M | const auto* digits_start = p; |
823 | | |
824 | 12.0M | int32_t n32 = 0; |
825 | | |
826 | | // This is the largest absolute int32 value we can handle before |
827 | | // risking overflow *on the next digit* (214748363). |
828 | 12.0M | static constexpr int32_t kMaxInt32 = (std::numeric_limits<int32_t>::max() - 9) / 10; |
829 | | |
830 | 12.0M | if (is_digit(*p)) { |
831 | 11.1M | n32 = (*p++ - '0'); |
832 | 14.4M | for (;;) { |
833 | 14.4M | if (!is_digit(*p) || n32 > kMaxInt32) break; |
834 | 3.30M | n32 = n32 * 10 + (*p++ - '0'); |
835 | 3.30M | } |
836 | 11.1M | } |
837 | | |
838 | 12.0M | if (!is_numeric(*p)) { |
839 | | // Did we actually match any digits? |
840 | 10.1M | if (p > digits_start) { |
841 | 10.1M | this->pushInt32(sign * n32); |
842 | 10.1M | return p; |
843 | 10.1M | } |
844 | 27.9k | return nullptr; |
845 | 10.1M | } |
846 | | |
847 | 1.93M | if (*p == '.') { |
848 | 1.86M | const auto* decimals_start = ++p; |
849 | | |
850 | 1.86M | int exp = 0; |
851 | | |
852 | 6.54M | for (;;) { |
853 | 6.54M | if (!is_digit(*p) || n32 > kMaxInt32) break; |
854 | 6.15M | n32 = n32 * 10 + (*p++ - '0'); --exp; |
855 | 6.15M | if (!is_digit(*p) || n32 > kMaxInt32) break; |
856 | 4.67M | n32 = n32 * 10 + (*p++ - '0'); --exp; |
857 | 4.67M | } |
858 | | |
859 | 1.86M | if (!is_numeric(*p)) { |
860 | | // Did we actually match any digits? |
861 | 1.74M | if (p > decimals_start) { |
862 | 1.69M | this->pushFloat(sign * n32 * pow10(exp)); |
863 | 1.69M | return p; |
864 | 1.69M | } |
865 | 50.4k | return nullptr; |
866 | 1.74M | } |
867 | | |
868 | 123k | if (n32 > kMaxInt32) { |
869 | | // we ran out on n32 bits |
870 | 43.4k | return this->matchFastFloatDecimalPart(p, sign, n32, exp); |
871 | 43.4k | } |
872 | 123k | } |
873 | | |
874 | 149k | return this->matchFastFloatPart(p, sign, n32); |
875 | 1.93M | } |
876 | | |
877 | 12.0M | const char* matchNumber(const char* p) { |
878 | 12.0M | if (const auto* fast = this->matchFast32OrFloat(p)) return fast; |
879 | | |
880 | | // slow fallback |
881 | 117k | char* matched; |
882 | 117k | float f = strtof(p, &matched); |
883 | 117k | if (matched > p) { |
884 | 117k | this->pushFloat(f); |
885 | 117k | return matched; |
886 | 117k | } |
887 | 174 | return this->error(nullptr, p, "invalid numeric token"); |
888 | 117k | } |
889 | | }; |
890 | | |
891 | 9.54M | void Write(const Value& v, SkWStream* stream) { |
892 | 9.54M | switch (v.getType()) { |
893 | 1.10k | case Value::Type::kNull: |
894 | 1.10k | stream->writeText("null"); |
895 | 1.10k | break; |
896 | 500 | case Value::Type::kBool: |
897 | 500 | stream->writeText(*v.as<BoolValue>() ? "true" : "false"); |
898 | 500 | break; |
899 | 6.62M | case Value::Type::kNumber: |
900 | 6.62M | stream->writeScalarAsText(*v.as<NumberValue>()); |
901 | 6.62M | break; |
902 | 717k | case Value::Type::kString: |
903 | 717k | stream->writeText("\""); |
904 | 717k | stream->writeText(v.as<StringValue>().begin()); |
905 | 717k | stream->writeText("\""); |
906 | 717k | break; |
907 | 2.19M | case Value::Type::kArray: { |
908 | 2.19M | const auto& array = v.as<ArrayValue>(); |
909 | 2.19M | stream->writeText("["); |
910 | 2.19M | bool first_value = true; |
911 | 8.11M | for (const auto& entry : array) { |
912 | 8.11M | if (!first_value) stream->writeText(","); |
913 | 8.11M | Write(entry, stream); |
914 | 8.11M | first_value = false; |
915 | 8.11M | } |
916 | 2.19M | stream->writeText("]"); |
917 | 2.19M | break; |
918 | 0 | } |
919 | 785 | case Value::Type::kObject: |
920 | 785 | const auto& object = v.as<ObjectValue>(); |
921 | 785 | stream->writeText("{"); |
922 | 785 | bool first_member = true; |
923 | 716k | for (const auto& member : object) { |
924 | 716k | SkASSERT(member.fKey.getType() == Value::Type::kString); |
925 | 716k | if (!first_member) stream->writeText(","); |
926 | 716k | Write(member.fKey, stream); |
927 | 716k | stream->writeText(":"); |
928 | 716k | Write(member.fValue, stream); |
929 | 716k | first_member = false; |
930 | 716k | } |
931 | 785 | stream->writeText("}"); |
932 | 785 | break; |
933 | 9.54M | } |
934 | 9.54M | } SkJSON.cpp:skjson::(anonymous namespace)::Write(skjson::Value const&, SkWStream*) Line | Count | Source | 891 | 9.54M | void Write(const Value& v, SkWStream* stream) { | 892 | 9.54M | switch (v.getType()) { | 893 | 1.10k | case Value::Type::kNull: | 894 | 1.10k | stream->writeText("null"); | 895 | 1.10k | break; | 896 | 500 | case Value::Type::kBool: | 897 | 500 | stream->writeText(*v.as<BoolValue>() ? "true" : "false"); | 898 | 500 | break; | 899 | 6.62M | case Value::Type::kNumber: | 900 | 6.62M | stream->writeScalarAsText(*v.as<NumberValue>()); | 901 | 6.62M | break; | 902 | 717k | case Value::Type::kString: | 903 | 717k | stream->writeText("\""); | 904 | 717k | stream->writeText(v.as<StringValue>().begin()); | 905 | 717k | stream->writeText("\""); | 906 | 717k | break; | 907 | 2.19M | case Value::Type::kArray: { | 908 | 2.19M | const auto& array = v.as<ArrayValue>(); | 909 | 2.19M | stream->writeText("["); | 910 | 2.19M | bool first_value = true; | 911 | 8.11M | for (const auto& entry : array) { | 912 | 8.11M | if (!first_value) stream->writeText(","); | 913 | 8.11M | Write(entry, stream); | 914 | 8.11M | first_value = false; | 915 | 8.11M | } | 916 | 2.19M | stream->writeText("]"); | 917 | 2.19M | break; | 918 | 0 | } | 919 | 785 | case Value::Type::kObject: | 920 | 785 | const auto& object = v.as<ObjectValue>(); | 921 | 785 | stream->writeText("{"); | 922 | 785 | bool first_member = true; | 923 | 716k | for (const auto& member : object) { | 924 | 716k | SkASSERT(member.fKey.getType() == Value::Type::kString); | 925 | 716k | if (!first_member) stream->writeText(","); | 926 | 716k | Write(member.fKey, stream); | 927 | 716k | stream->writeText(":"); | 928 | 716k | Write(member.fValue, stream); | 929 | 716k | first_member = false; | 930 | 716k | } | 931 | 785 | stream->writeText("}"); | 932 | 785 | break; | 933 | 9.54M | } | 934 | 9.54M | } |
Unexecuted instantiation: SkJSON.cpp:skjson::(anonymous namespace)::Write(skjson::Value const&, SkWStream*) |
935 | | |
936 | | } // namespace |
937 | | |
938 | 0 | SkString Value::toString() const { |
939 | 0 | SkDynamicMemoryWStream wstream; |
940 | 0 | Write(*this, &wstream); |
941 | 0 | const auto data = wstream.detachAsData(); |
942 | | // TODO: is there a better way to pass data around without copying? |
943 | 0 | return SkString(static_cast<const char*>(data->data()), data->size()); |
944 | 0 | } |
945 | | |
946 | | static constexpr size_t kMinChunkSize = 4096; |
947 | | |
948 | | DOM::DOM(const char* data, size_t size) |
949 | 12.0k | : fAlloc(kMinChunkSize) { |
950 | 12.0k | DOMParser parser(fAlloc); |
951 | | |
952 | 12.0k | fRoot = parser.parse(data, size); |
953 | 12.0k | } |
954 | | |
955 | 2.08k | void DOM::write(SkWStream* stream) const { |
956 | 2.08k | Write(fRoot, stream); |
957 | 2.08k | } |
958 | | |
959 | | } // namespace skjson |