/src/hermes/lib/VM/JSLib/RuntimeJSONUtils.cpp
Line | Count | Source |
1 | | /* |
2 | | * Copyright (c) Meta Platforms, Inc. and affiliates. |
3 | | * |
4 | | * This source code is licensed under the MIT license found in the |
5 | | * LICENSE file in the root directory of this source tree. |
6 | | */ |
7 | | |
8 | | #include "hermes/VM/JSLib/RuntimeJSONUtils.h" |
9 | | |
10 | | #include "Object.h" |
11 | | |
12 | | #include "hermes/Support/Compiler.h" |
13 | | #include "hermes/Support/JSON.h" |
14 | | #include "hermes/Support/UTF16Stream.h" |
15 | | #include "hermes/VM/ArrayLike.h" |
16 | | #include "hermes/VM/ArrayStorage.h" |
17 | | #include "hermes/VM/Callable.h" |
18 | | #include "hermes/VM/JSArray.h" |
19 | | #include "hermes/VM/JSProxy.h" |
20 | | #include "hermes/VM/PrimitiveBox.h" |
21 | | |
22 | | #include "JSONLexer.h" |
23 | | |
24 | | #include "llvh/ADT/SmallString.h" |
25 | | #include "llvh/Support/SaveAndRestore.h" |
26 | | |
27 | | namespace hermes { |
28 | | namespace vm { |
29 | | |
30 | | namespace { |
31 | | |
32 | | /// This class wraps the functionality required to parse a JSON string into |
33 | | /// a VM runtime value. It expects a UTF8 string as input, and returns a |
34 | | /// HermesValue when parse is called. |
35 | | class RuntimeJSONParser { |
36 | | public: |
37 | | static constexpr int32_t MAX_RECURSION_DEPTH = |
38 | | #ifndef HERMES_LIMIT_STACK_DEPTH |
39 | | 512 |
40 | | #else |
41 | | 100 |
42 | | #endif |
43 | | ; |
44 | | |
45 | | private: |
46 | | /// The VM runtime. |
47 | | Runtime &runtime_; |
48 | | |
49 | | /// The lexer. |
50 | | JSONLexer lexer_; |
51 | | |
52 | | /// Stores the optional reviver parameter. |
53 | | /// https://es5.github.io/#x15.12.2 |
54 | | Handle<Callable> reviver_; |
55 | | |
56 | | /// A temporary handle, to avoid creating new handles when a temporary one |
57 | | /// is needed to protect some HermesValue. |
58 | | MutableHandle<> tmpHandle_; |
59 | | |
60 | | /// How many more nesting levels we allow before error. |
61 | | /// Decremented every time a nested level is started, |
62 | | /// and incremented again when leaving the nest. |
63 | | /// If it drops below 0 while parsing, raise a stack overflow. |
64 | | int32_t remainingDepth_{MAX_RECURSION_DEPTH}; |
65 | | |
66 | | public: |
67 | | explicit RuntimeJSONParser( |
68 | | Runtime &runtime, |
69 | | UTF16Stream &&jsonString, |
70 | | Handle<Callable> reviver) |
71 | 0 | : runtime_(runtime), |
72 | 0 | lexer_(runtime, std::move(jsonString)), |
73 | 0 | reviver_(reviver), |
74 | 0 | tmpHandle_(runtime) {} |
75 | | |
76 | | /// Parse JSON string through lexer_, create objects using runtime_. |
77 | | /// If errors occur, this function will return undefined, and the error |
78 | | /// should be kept inside SourceErrorManager. |
79 | | CallResult<HermesValue> parse(); |
80 | | |
81 | | private: |
82 | | /// Parse a JSON value, starting from the current token. |
83 | | /// When this function is finished, the current token will be set |
84 | | /// to the next token after the current parsed value. |
85 | | CallResult<HermesValue> parseValue(); |
86 | | |
87 | | /// Parse a JSON array, starting from the "[" token. |
88 | | /// When this function is finished, the current token must be "]". |
89 | | CallResult<HermesValue> parseArray(); |
90 | | |
91 | | /// Parse a JSON object, starting from the "{" token. |
92 | | /// When this function is finished, the current token must be "}". |
93 | | CallResult<HermesValue> parseObject(); |
94 | | |
95 | | /// Use reviver to filter the result. |
96 | | CallResult<HermesValue> revive(Handle<> value); |
97 | | |
98 | | /// The abstract operation Walk is a recursive abstract operation that takes |
99 | | /// two parameters: a holder object and the String name of a property in |
100 | | CallResult<HermesValue> operationWalk( |
101 | | Handle<JSObject> holder, |
102 | | Handle<> property); |
103 | | |
104 | | /// Given value and key, recursively call operationWalk to generate a new |
105 | | /// filtered value. It's a helper function for operationWalk, and does not |
106 | | /// need to return a value |
107 | | ExecutionStatus filter(Handle<JSObject> val, Handle<> key); |
108 | | }; |
109 | | |
110 | | /// This class wraps the functionality required to stringify an object |
111 | | /// as JSON. |
112 | | class JSONStringifyer { |
113 | | /// The runtime. |
114 | | Runtime &runtime_; |
115 | | |
116 | | /// The ReplacerFunction, initialized from the "replacer" argument in |
117 | | /// stringify. |
118 | | MutableHandle<Callable> replacerFunction_; |
119 | | |
120 | | /// The gap string, which will be used to construct indent. |
121 | | /// Initialized from the "space" argument in stringify. |
122 | | /// gap_ will be nullptr if no gap is specified or if the gap is an empty |
123 | | /// string. This means that if gap_ is not nullptr, it will not be |
124 | | /// an empty string. |
125 | | MutableHandle<StringPrimitive> gap_; |
126 | | |
127 | | /// The PropertyList, constructed from the "replacer" argument in stringify. |
128 | | MutableHandle<JSArray> propertyList_; |
129 | | |
130 | | /// The stack, used at runtime by operationJA and operationJO to store |
131 | | /// `value_` for recursions. |
132 | | MutableHandle<PropStorage> stackValue_; |
133 | | |
134 | | /// An additional stack just for operationJO to store `K` for recursions. |
135 | | MutableHandle<PropStorage> stackJO_; |
136 | | |
137 | | /// A temporary handle, to avoid creating new handles when a temporary one |
138 | | /// is needed. |
139 | | /// Note: this should be used with care because it can be shared among |
140 | | /// functions. |
141 | | MutableHandle<> tmpHandle_; |
142 | | |
143 | | /// A second temporary handle for when the first is taken. |
144 | | MutableHandle<> tmpHandle2_; |
145 | | |
146 | | /// Handle used by operationStr to store the value. |
147 | | MutableHandle<> operationStrValue_; |
148 | | |
149 | | /// Handle used by operationJO to store K. |
150 | | MutableHandle<JSArray> operationJOK_; |
151 | | |
152 | | /// The holder argument passed to operationStr. |
153 | | /// We define a member variable here to avoid creating a new handle |
154 | | /// each time we are calling operationStr. |
155 | | MutableHandle<JSObject> operationStrHolder_; |
156 | | |
157 | | /// The current depth of recursion, used at runtime by operationJA and |
158 | | /// operationJO. This is used as a stack overflow guard in stringifying. It |
159 | | /// also doubles as an indent counter for prettified JS. |
160 | | uint32_t depthCount_{0}; |
161 | | |
162 | | /// The max amount that depthCount_ is allowed to reach. Once it's reached, an |
163 | | /// exception will be thrown. |
164 | | static constexpr uint32_t MAX_RECURSION_DEPTH{ |
165 | | RuntimeJSONParser::MAX_RECURSION_DEPTH}; |
166 | | |
167 | | /// The output buffer. The serialization process will append into it. |
168 | | llvh::SmallVector<char16_t, 32> output_{}; |
169 | | |
170 | | public: |
171 | | explicit JSONStringifyer(Runtime &runtime) |
172 | 0 | : runtime_(runtime), |
173 | 0 | replacerFunction_(runtime), |
174 | 0 | gap_{runtime, nullptr}, |
175 | 0 | propertyList_(runtime), |
176 | 0 | stackValue_(runtime), |
177 | 0 | stackJO_(runtime), |
178 | 0 | tmpHandle_(runtime), |
179 | 0 | tmpHandle2_(runtime), |
180 | 0 | operationStrValue_(runtime), |
181 | 0 | operationJOK_(runtime), |
182 | 0 | operationStrHolder_(runtime) {} |
183 | | |
184 | 0 | LLVM_NODISCARD ExecutionStatus init(Handle<> replacer, Handle<> space) { |
185 | 0 | auto arrRes = PropStorage::create(runtime_, 4); |
186 | 0 | if (LLVM_UNLIKELY(arrRes == ExecutionStatus::EXCEPTION)) { |
187 | 0 | return ExecutionStatus::EXCEPTION; |
188 | 0 | } |
189 | 0 | stackValue_ = vmcast<PropStorage>(*arrRes); |
190 | 0 | if (LLVM_UNLIKELY( |
191 | 0 | (arrRes = PropStorage::create(runtime_, 4)) == |
192 | 0 | ExecutionStatus::EXCEPTION)) { |
193 | 0 | return ExecutionStatus::EXCEPTION; |
194 | 0 | } |
195 | 0 | stackJO_ = vmcast<PropStorage>(*arrRes); |
196 | 0 | auto cr = initializeReplacer(replacer); |
197 | 0 | if (LLVM_UNLIKELY(cr == ExecutionStatus::EXCEPTION)) { |
198 | 0 | return ExecutionStatus::EXCEPTION; |
199 | 0 | } |
200 | 0 | return initializeSpace(space); |
201 | 0 | } |
202 | | |
203 | | /// Stringify \p value. |
204 | | CallResult<HermesValue> stringify(Handle<> value); |
205 | | |
206 | | private: |
207 | | /// Check the type of replacer, initialize |
208 | | /// ReplacerFunction (replacerFunction_) and PropertyList (propertyList_). |
209 | | /// Covers step 3 and 4 in ES5.1 15.12.3. |
210 | | LLVM_NODISCARD ExecutionStatus initializeReplacer(Handle<> replacer); |
211 | | |
212 | | /// Check the type of space, initialize gap (gap_). |
213 | | /// Covers step 5, 6, 7, 8 in ES5.1 15.12.3. |
214 | | ExecutionStatus initializeSpace(Handle<> space); |
215 | | |
216 | | /// Implement the Str(key, holder) abstract operation to serialize a value. |
217 | | /// According to the spec, \p key should always be a string. |
218 | | /// However if this function is called from operationJA, we |
219 | | /// we don't want to convert every index into string. |
220 | | /// Hence we leave the key as it is, and convert them to string if needed. |
221 | | /// The holder is always stored in operationStrHolder_ by caller. |
222 | | /// \return whether the result is not undefined. |
223 | | CallResult<bool> operationStr(HermesValue key); |
224 | | |
225 | | /// Implement the abstract operation Quote(value). |
226 | | /// It wraps a String value in double quotes and escapes characters within it. |
227 | | void operationQuote(StringView value); |
228 | | |
229 | | /// Implement the abstract operation JA(value). The value to operate on |
230 | | /// is always the current last element in stackValue_. |
231 | | /// It serializes an array. |
232 | | ExecutionStatus operationJA(); |
233 | | |
234 | | /// Implement the abstract operation JO(value). The value to operate on |
235 | | /// is always the current last element in stackValue_. |
236 | | /// It serializes an object. |
237 | | ExecutionStatus operationJO(); |
238 | | |
239 | | /// Append '\n' and indent to output_. |
240 | | /// The indent is constructed according to depthCount_. |
241 | | void indent(); |
242 | | |
243 | | /// Push a value to stack when traversing the object recursively. |
244 | | /// It also checks if the value already exsists in the stack for |
245 | | /// cyclic recursion. |
246 | | /// \return true if the push is successful (no cyclic). |
247 | | CallResult<bool> pushValueToStack(HermesValue value); |
248 | | |
249 | | /// Pop the top of stack. |
250 | | void popValueFromStack(); |
251 | | |
252 | | /// Append the string indicated as \p identifierID to output_. |
253 | | void appendToOutput(SymbolID identifierID); |
254 | | |
255 | | /// Append the string indicated as \p str to output_. |
256 | | void appendToOutput(const StringPrimitive *str); |
257 | | }; |
258 | | } // namespace |
259 | | |
260 | 0 | CallResult<HermesValue> RuntimeJSONParser::parse() { |
261 | | // parseValue() requires one token to start with. |
262 | 0 | if (LLVM_UNLIKELY(lexer_.advance() == ExecutionStatus::EXCEPTION)) { |
263 | 0 | return ExecutionStatus::EXCEPTION; |
264 | 0 | } |
265 | 0 | auto parRes = parseValue(); |
266 | 0 | if (LLVM_UNLIKELY(parRes == ExecutionStatus::EXCEPTION)) { |
267 | 0 | return ExecutionStatus::EXCEPTION; |
268 | 0 | } |
269 | | |
270 | | // Make sure the next token must be EOF. |
271 | 0 | if (LLVM_UNLIKELY(lexer_.getCurToken()->getKind() != JSONTokenKind::Eof)) { |
272 | 0 | return lexer_.errorUnexpectedChar(); |
273 | 0 | } |
274 | | |
275 | 0 | if (reviver_.get()) { |
276 | 0 | if ((parRes = revive(runtime_.makeHandle(*parRes))) == |
277 | 0 | ExecutionStatus::EXCEPTION) |
278 | 0 | return ExecutionStatus::EXCEPTION; |
279 | 0 | } |
280 | 0 | return parRes; |
281 | 0 | } |
282 | | |
283 | 0 | CallResult<HermesValue> RuntimeJSONParser::parseValue() { |
284 | 0 | llvh::SaveAndRestore<decltype(remainingDepth_)> oldDepth{ |
285 | 0 | remainingDepth_, remainingDepth_ - 1}; |
286 | 0 | if (remainingDepth_ <= 0) { |
287 | 0 | return runtime_.raiseStackOverflow(Runtime::StackOverflowKind::JSONParser); |
288 | 0 | } |
289 | | |
290 | 0 | MutableHandle<> returnValue{runtime_}; |
291 | 0 | switch (lexer_.getCurToken()->getKind()) { |
292 | 0 | case JSONTokenKind::String: |
293 | 0 | returnValue = lexer_.getCurToken()->getStrAsPrim().getHermesValue(); |
294 | 0 | break; |
295 | 0 | case JSONTokenKind::Number: |
296 | 0 | returnValue = HermesValue::encodeUntrustedNumberValue( |
297 | 0 | lexer_.getCurToken()->getNumber()); |
298 | 0 | break; |
299 | 0 | case JSONTokenKind::LBrace: { |
300 | 0 | auto parRes = parseObject(); |
301 | 0 | if (LLVM_UNLIKELY(parRes == ExecutionStatus::EXCEPTION)) { |
302 | 0 | return ExecutionStatus::EXCEPTION; |
303 | 0 | } |
304 | 0 | returnValue = *parRes; |
305 | 0 | break; |
306 | 0 | } |
307 | 0 | case JSONTokenKind::LSquare: { |
308 | 0 | auto parRes = parseArray(); |
309 | 0 | if (LLVM_UNLIKELY(parRes == ExecutionStatus::EXCEPTION)) { |
310 | 0 | return ExecutionStatus::EXCEPTION; |
311 | 0 | } |
312 | 0 | returnValue = *parRes; |
313 | 0 | break; |
314 | 0 | } |
315 | 0 | case JSONTokenKind::True: |
316 | 0 | returnValue = HermesValue::encodeBoolValue(true); |
317 | 0 | break; |
318 | 0 | case JSONTokenKind::False: |
319 | 0 | returnValue = HermesValue::encodeBoolValue(false); |
320 | 0 | break; |
321 | 0 | case JSONTokenKind::Null: |
322 | 0 | returnValue = HermesValue::encodeNullValue(); |
323 | 0 | break; |
324 | | |
325 | 0 | default: |
326 | 0 | return lexer_.errorUnexpectedChar(); |
327 | 0 | } |
328 | | |
329 | 0 | if (LLVM_UNLIKELY(lexer_.advance() == ExecutionStatus::EXCEPTION)) { |
330 | 0 | return ExecutionStatus::EXCEPTION; |
331 | 0 | } |
332 | | |
333 | 0 | return returnValue.getHermesValue(); |
334 | 0 | } |
335 | | |
336 | 0 | CallResult<HermesValue> RuntimeJSONParser::parseArray() { |
337 | 0 | assert( |
338 | 0 | lexer_.getCurToken()->getKind() == JSONTokenKind::LSquare && |
339 | 0 | "Wrong entrance to parseArray"); |
340 | 0 | auto arrRes = JSArray::create(runtime_, 4, 0); |
341 | 0 | if (LLVM_UNLIKELY(arrRes == ExecutionStatus::EXCEPTION)) { |
342 | 0 | return ExecutionStatus::EXCEPTION; |
343 | 0 | } |
344 | 0 | auto array = *arrRes; |
345 | |
|
346 | 0 | if (LLVM_UNLIKELY(lexer_.advance() == ExecutionStatus::EXCEPTION)) { |
347 | 0 | return ExecutionStatus::EXCEPTION; |
348 | 0 | } |
349 | 0 | if (lexer_.getCurToken()->getKind() != JSONTokenKind::RSquare) { |
350 | 0 | MutableHandle<> indexValue{runtime_}; |
351 | 0 | GCScope gcScope{runtime_}; |
352 | 0 | auto marker = gcScope.createMarker(); |
353 | |
|
354 | 0 | for (uint32_t index = 0;; ++index) { |
355 | 0 | gcScope.flushToMarker(marker); |
356 | |
|
357 | 0 | auto parRes = parseValue(); |
358 | 0 | if (LLVM_UNLIKELY(parRes == ExecutionStatus::EXCEPTION)) { |
359 | 0 | return ExecutionStatus::EXCEPTION; |
360 | 0 | } |
361 | | |
362 | 0 | indexValue = HermesValue::encodeUntrustedNumberValue(index); |
363 | 0 | (void)JSObject::defineOwnComputedPrimitive( |
364 | 0 | array, |
365 | 0 | runtime_, |
366 | 0 | indexValue, |
367 | 0 | DefinePropertyFlags::getDefaultNewPropertyFlags(), |
368 | 0 | runtime_.makeHandle(*parRes)); |
369 | |
|
370 | 0 | if (lexer_.getCurToken()->getKind() == JSONTokenKind::Comma) { |
371 | 0 | if (LLVM_UNLIKELY(lexer_.advance() == ExecutionStatus::EXCEPTION)) { |
372 | 0 | return ExecutionStatus::EXCEPTION; |
373 | 0 | } |
374 | 0 | continue; |
375 | 0 | } else if (lexer_.getCurToken()->getKind() == JSONTokenKind::RSquare) { |
376 | 0 | break; |
377 | 0 | } else { |
378 | 0 | return lexer_.errorUnexpectedChar(); |
379 | 0 | } |
380 | 0 | } |
381 | 0 | assert( |
382 | 0 | lexer_.getCurToken()->getKind() == JSONTokenKind::RSquare && |
383 | 0 | "Unexpected break for array parse"); |
384 | 0 | } |
385 | | |
386 | 0 | return array.getHermesValue(); |
387 | 0 | } |
388 | | |
389 | 0 | CallResult<HermesValue> RuntimeJSONParser::parseObject() { |
390 | 0 | assert( |
391 | 0 | lexer_.getCurToken()->getKind() == JSONTokenKind::LBrace && |
392 | 0 | "Wrong entrance to parseObject"); |
393 | 0 | auto object = runtime_.makeHandle(JSObject::create(runtime_)); |
394 | | |
395 | | // If the lexer encounters a string in this context, it should treat it as a |
396 | | // key string, which means it will store the string as a symbol. |
397 | 0 | if (LLVM_UNLIKELY( |
398 | 0 | lexer_.advanceStrAsSymbol() == ExecutionStatus::EXCEPTION)) { |
399 | 0 | return ExecutionStatus::EXCEPTION; |
400 | 0 | } |
401 | 0 | if (lexer_.getCurToken()->getKind() == JSONTokenKind::RBrace) { |
402 | 0 | return object.getHermesValue(); |
403 | 0 | } |
404 | | |
405 | 0 | MutableHandle<SymbolID> key{runtime_}; |
406 | 0 | GCScope gcScope{runtime_}; |
407 | 0 | auto marker = gcScope.createMarker(); |
408 | 0 | for (;;) { |
409 | 0 | gcScope.flushToMarker(marker); |
410 | |
|
411 | 0 | if (LLVM_UNLIKELY( |
412 | 0 | lexer_.getCurToken()->getKind() != JSONTokenKind::String)) { |
413 | 0 | return lexer_.error("Expect a string key in JSON object"); |
414 | 0 | } |
415 | 0 | key = lexer_.getCurToken()->getStrAsSymbol().get(); |
416 | |
|
417 | 0 | if (LLVM_UNLIKELY(lexer_.advance() == ExecutionStatus::EXCEPTION)) { |
418 | 0 | return ExecutionStatus::EXCEPTION; |
419 | 0 | } |
420 | | |
421 | 0 | if (lexer_.getCurToken()->getKind() != JSONTokenKind::Colon) { |
422 | 0 | return lexer_.error("Expect ':' after the key in JSON object"); |
423 | 0 | } |
424 | | |
425 | 0 | if (LLVM_UNLIKELY(lexer_.advance() == ExecutionStatus::EXCEPTION)) { |
426 | 0 | return ExecutionStatus::EXCEPTION; |
427 | 0 | } |
428 | | |
429 | 0 | auto parRes = parseValue(); |
430 | 0 | if (LLVM_UNLIKELY(parRes == ExecutionStatus::EXCEPTION)) { |
431 | 0 | return ExecutionStatus::EXCEPTION; |
432 | 0 | } |
433 | | |
434 | 0 | (void)JSObject::defineOwnComputedPrimitive( |
435 | 0 | object, |
436 | 0 | runtime_, |
437 | 0 | key, |
438 | 0 | DefinePropertyFlags::getDefaultNewPropertyFlags(), |
439 | 0 | runtime_.makeHandle(*parRes)); |
440 | |
|
441 | 0 | if (lexer_.getCurToken()->getKind() == JSONTokenKind::Comma) { |
442 | 0 | if (LLVM_UNLIKELY( |
443 | 0 | lexer_.advanceStrAsSymbol() == ExecutionStatus::EXCEPTION)) { |
444 | 0 | return ExecutionStatus::EXCEPTION; |
445 | 0 | } |
446 | 0 | continue; |
447 | 0 | } else if (lexer_.getCurToken()->getKind() == JSONTokenKind::RBrace) { |
448 | 0 | break; |
449 | 0 | } else { |
450 | 0 | return lexer_.errorUnexpectedChar(); |
451 | 0 | } |
452 | 0 | } |
453 | 0 | assert( |
454 | 0 | lexer_.getCurToken()->getKind() == JSONTokenKind::RBrace && |
455 | 0 | "Unexpected stop for object parse"); |
456 | 0 | return object.getHermesValue(); |
457 | 0 | } |
458 | | |
459 | 0 | CallResult<HermesValue> RuntimeJSONParser::revive(Handle<> value) { |
460 | 0 | auto root = runtime_.makeHandle(JSObject::create(runtime_)); |
461 | 0 | auto status = JSObject::defineOwnProperty( |
462 | 0 | root, |
463 | 0 | runtime_, |
464 | 0 | Predefined::getSymbolID(Predefined::emptyString), |
465 | 0 | DefinePropertyFlags::getDefaultNewPropertyFlags(), |
466 | 0 | value); |
467 | 0 | (void)status; |
468 | 0 | assert( |
469 | 0 | status != ExecutionStatus::EXCEPTION && *status && |
470 | 0 | "defineOwnProperty on new object cannot fail"); |
471 | 0 | return operationWalk( |
472 | 0 | root, runtime_.getPredefinedStringHandle(Predefined::emptyString)); |
473 | 0 | } |
474 | | |
475 | | CallResult<HermesValue> RuntimeJSONParser::operationWalk( |
476 | | Handle<JSObject> holder, |
477 | 0 | Handle<> property) { |
478 | | // The operation is recursive so it needs a GCScope. |
479 | 0 | GCScope gcScope(runtime_); |
480 | |
|
481 | 0 | llvh::SaveAndRestore<decltype(remainingDepth_)> oldDepth{ |
482 | 0 | remainingDepth_, remainingDepth_ - 1}; |
483 | 0 | if (remainingDepth_ <= 0) { |
484 | 0 | return runtime_.raiseStackOverflow(Runtime::StackOverflowKind::JSONParser); |
485 | 0 | } |
486 | | |
487 | 0 | auto propRes = JSObject::getComputed_RJS(holder, runtime_, property); |
488 | 0 | if (LLVM_UNLIKELY(propRes == ExecutionStatus::EXCEPTION)) { |
489 | 0 | return ExecutionStatus::EXCEPTION; |
490 | 0 | } |
491 | 0 | MutableHandle<> tmpHandle{runtime_}; |
492 | 0 | CallResult<bool> isArrayRes = |
493 | 0 | isArray(runtime_, dyn_vmcast<JSObject>(propRes->get())); |
494 | 0 | if (LLVM_UNLIKELY(isArrayRes == ExecutionStatus::EXCEPTION)) { |
495 | 0 | return ExecutionStatus::EXCEPTION; |
496 | 0 | } |
497 | 0 | auto valHandle = runtime_.makeHandle(std::move(*propRes)); |
498 | 0 | if (*isArrayRes) { |
499 | 0 | Handle<JSObject> objHandle = Handle<JSObject>::vmcast(valHandle); |
500 | 0 | CallResult<uint64_t> lenRes = getArrayLikeLength_RJS(objHandle, runtime_); |
501 | 0 | if (LLVM_UNLIKELY(lenRes == ExecutionStatus::EXCEPTION)) { |
502 | 0 | return ExecutionStatus::EXCEPTION; |
503 | 0 | } |
504 | | |
505 | 0 | GCScopeMarkerRAII marker(runtime_); |
506 | 0 | for (uint64_t index = 0, e = *lenRes; index < e; ++index) { |
507 | 0 | tmpHandle = HermesValue::encodeUntrustedNumberValue(index); |
508 | | // Note that deleting elements doesn't affect array length. |
509 | 0 | if (LLVM_UNLIKELY( |
510 | 0 | filter(objHandle, tmpHandle) == ExecutionStatus::EXCEPTION)) { |
511 | 0 | return ExecutionStatus::EXCEPTION; |
512 | 0 | } |
513 | 0 | marker.flush(); |
514 | 0 | } |
515 | 0 | } else if (auto scopedObject = Handle<JSObject>::dyn_vmcast(valHandle)) { |
516 | 0 | auto cr = JSObject::getOwnPropertyNames(scopedObject, runtime_, true); |
517 | 0 | if (cr == ExecutionStatus::EXCEPTION) { |
518 | 0 | return ExecutionStatus::EXCEPTION; |
519 | 0 | } |
520 | 0 | auto keys = *cr; |
521 | 0 | GCScopeMarkerRAII marker(runtime_); |
522 | 0 | for (uint32_t index = 0, e = keys->getEndIndex(); index < e; ++index) { |
523 | 0 | tmpHandle = keys->at(runtime_, index).unboxToHV(runtime_); |
524 | 0 | if (LLVM_UNLIKELY( |
525 | 0 | filter(scopedObject, tmpHandle) == ExecutionStatus::EXCEPTION)) { |
526 | 0 | return ExecutionStatus::EXCEPTION; |
527 | 0 | } |
528 | 0 | marker.flush(); |
529 | 0 | } |
530 | 0 | } |
531 | | // We have delayed converting the property to a string if it was index. |
532 | | // Now we have to do it because we are passing it to the reviver. |
533 | 0 | tmpHandle = *property; |
534 | 0 | auto strRes = toString_RJS(runtime_, tmpHandle); |
535 | 0 | if (LLVM_UNLIKELY(strRes == ExecutionStatus::EXCEPTION)) { |
536 | 0 | return ExecutionStatus::EXCEPTION; |
537 | 0 | } |
538 | 0 | tmpHandle = strRes->getHermesValue(); |
539 | |
|
540 | 0 | return Callable::executeCall2( |
541 | 0 | reviver_, runtime_, holder, *tmpHandle, *valHandle) |
542 | 0 | .toCallResultHermesValue(); |
543 | 0 | } |
544 | | |
545 | 0 | ExecutionStatus RuntimeJSONParser::filter(Handle<JSObject> val, Handle<> key) { |
546 | 0 | auto jsonRes = operationWalk(val, key); |
547 | 0 | if (LLVM_UNLIKELY(jsonRes == ExecutionStatus::EXCEPTION)) { |
548 | 0 | return ExecutionStatus::EXCEPTION; |
549 | 0 | } |
550 | 0 | auto newElement = runtime_.makeHandle(*jsonRes); |
551 | 0 | if (newElement->isUndefined()) { |
552 | 0 | if (LLVM_UNLIKELY( |
553 | 0 | JSObject::deleteComputed(val, runtime_, key) == |
554 | 0 | ExecutionStatus::EXCEPTION)) { |
555 | 0 | return ExecutionStatus::EXCEPTION; |
556 | 0 | } |
557 | 0 | } else { |
558 | 0 | if (LLVM_UNLIKELY( |
559 | 0 | JSObject::defineOwnComputed( |
560 | 0 | val, |
561 | 0 | runtime_, |
562 | 0 | key, |
563 | 0 | DefinePropertyFlags::getDefaultNewPropertyFlags(), |
564 | 0 | newElement) == ExecutionStatus::EXCEPTION)) { |
565 | 0 | return ExecutionStatus::EXCEPTION; |
566 | 0 | } |
567 | 0 | } |
568 | 0 | return ExecutionStatus::RETURNED; |
569 | 0 | } |
570 | | |
571 | | CallResult<HermesValue> runtimeJSONParse( |
572 | | Runtime &runtime, |
573 | | Handle<StringPrimitive> jsonString, |
574 | 0 | Handle<Callable> reviver) { |
575 | | // Our parser requires UTF16 data that does not move during GCs, so |
576 | | // in most cases we'll need to copy, except for external 16-bit strings. |
577 | 0 | UTF16Ref ref; |
578 | 0 | SmallU16String<32> storage; |
579 | 0 | if (LLVM_UNLIKELY(jsonString->isExternal() && !jsonString->isASCII())) { |
580 | 0 | ref = jsonString->getStringRef<char16_t>(); |
581 | 0 | } else { |
582 | 0 | StringPrimitive::createStringView(runtime, jsonString) |
583 | 0 | .appendUTF16String(storage); |
584 | 0 | ref = storage; |
585 | 0 | } |
586 | |
|
587 | 0 | RuntimeJSONParser parser{runtime, UTF16Stream(ref), reviver}; |
588 | 0 | return parser.parse(); |
589 | 0 | } |
590 | | |
591 | | CallResult<HermesValue> runtimeJSONParseRef( |
592 | | Runtime &runtime, |
593 | 0 | UTF16Stream &&stream) { |
594 | 0 | RuntimeJSONParser parser{ |
595 | 0 | runtime, std::move(stream), Runtime::makeNullHandle<Callable>()}; |
596 | 0 | return parser.parse(); |
597 | 0 | } |
598 | | |
599 | 0 | ExecutionStatus JSONStringifyer::initializeReplacer(Handle<> replacer) { |
600 | 0 | if (!vmisa<JSObject>(*replacer)) |
601 | 0 | return ExecutionStatus::RETURNED; |
602 | | // replacer is an object. |
603 | | |
604 | 0 | if ((replacerFunction_ = dyn_vmcast<Callable>(*replacer))) |
605 | 0 | return ExecutionStatus::RETURNED; |
606 | | // replacer is not a callable. |
607 | | |
608 | 0 | auto replacerArray = Handle<JSObject>::dyn_vmcast(replacer); |
609 | 0 | CallResult<bool> isArrayRes = |
610 | 0 | isArray(runtime_, dyn_vmcast<JSObject>(*replacerArray)); |
611 | 0 | if (LLVM_UNLIKELY(isArrayRes == ExecutionStatus::EXCEPTION)) { |
612 | 0 | return ExecutionStatus::EXCEPTION; |
613 | 0 | } |
614 | 0 | if (!*isArrayRes) |
615 | 0 | return ExecutionStatus::RETURNED; |
616 | | // replacer is arrayish |
617 | | |
618 | 0 | CallResult<uint64_t> lenRes = getArrayLikeLength_RJS(replacerArray, runtime_); |
619 | 0 | if (LLVM_UNLIKELY(lenRes == ExecutionStatus::EXCEPTION)) { |
620 | 0 | return ExecutionStatus::EXCEPTION; |
621 | 0 | } |
622 | 0 | if (*lenRes > UINT32_MAX) { |
623 | 0 | return runtime_.raiseRangeError("replacer array is too large"); |
624 | 0 | } |
625 | 0 | uint32_t len = static_cast<uint32_t>(*lenRes); |
626 | 0 | auto arrRes = JSArray::create(runtime_, len, 0); |
627 | 0 | if (LLVM_UNLIKELY(arrRes == ExecutionStatus::EXCEPTION)) { |
628 | 0 | return ExecutionStatus::EXCEPTION; |
629 | 0 | } |
630 | 0 | propertyList_ = arrRes->get(); |
631 | | |
632 | | // Iterate through all indexes, in ascending order. |
633 | 0 | GCScope gcScope{runtime_}; |
634 | 0 | auto marker = gcScope.createMarker(); |
635 | 0 | for (uint64_t i = 0, e = *lenRes; i < e; ++i) { |
636 | 0 | gcScope.flushToMarker(marker); |
637 | | |
638 | | // Get the property value. |
639 | 0 | tmpHandle_ = HermesValue::encodeUntrustedNumberValue(i); |
640 | 0 | auto propRes = |
641 | 0 | JSObject::getComputed_RJS(replacerArray, runtime_, tmpHandle_); |
642 | 0 | if (LLVM_UNLIKELY(propRes == ExecutionStatus::EXCEPTION)) { |
643 | 0 | return ExecutionStatus::EXCEPTION; |
644 | 0 | } |
645 | 0 | PseudoHandle<> v = std::move(*propRes); |
646 | | // Convert v to string and store into item, if v is string, number, JSString |
647 | | // or JSNumber. |
648 | 0 | if (v->isString()) { |
649 | 0 | tmpHandle_ = std::move(v); |
650 | 0 | } else if ( |
651 | 0 | v->isNumber() || vmisa<JSNumber>(v.get()) || vmisa<JSString>(v.get())) { |
652 | 0 | tmpHandle_ = std::move(v); |
653 | 0 | auto strRes = toString_RJS(runtime_, tmpHandle_); |
654 | 0 | if (LLVM_UNLIKELY(strRes == ExecutionStatus::EXCEPTION)) { |
655 | 0 | return ExecutionStatus::EXCEPTION; |
656 | 0 | } |
657 | 0 | tmpHandle_ = strRes->getHermesValue(); |
658 | 0 | } else { |
659 | 0 | tmpHandle_ = HermesValue::encodeUndefinedValue(); |
660 | 0 | } |
661 | | |
662 | 0 | if (tmpHandle_->isUndefined()) |
663 | 0 | continue; |
664 | | // We only add item to propertyList if item is not already an element. |
665 | 0 | bool exists = false; |
666 | 0 | auto len = propertyList_->getEndIndex(); |
667 | 0 | for (uint32_t i = 0; i < len; ++i) { |
668 | 0 | if (propertyList_->at(runtime_, i) |
669 | 0 | .getString(runtime_) |
670 | 0 | ->equals(tmpHandle_->getString())) { |
671 | 0 | exists = true; |
672 | 0 | break; |
673 | 0 | } |
674 | 0 | } |
675 | 0 | if (!exists) { |
676 | 0 | JSArray::setElementAt(propertyList_, runtime_, len, tmpHandle_); |
677 | 0 | } |
678 | 0 | } |
679 | 0 | return ExecutionStatus::RETURNED; |
680 | 0 | } |
681 | | |
682 | 0 | ExecutionStatus JSONStringifyer::initializeSpace(Handle<> space) { |
683 | 0 | tmpHandle_ = *space; |
684 | 0 | if (vmisa<JSNumber>(*tmpHandle_)) { |
685 | 0 | auto numRes = toNumber_RJS(runtime_, tmpHandle_); |
686 | 0 | if (LLVM_UNLIKELY(numRes == ExecutionStatus::EXCEPTION)) { |
687 | 0 | return ExecutionStatus::EXCEPTION; |
688 | 0 | } |
689 | 0 | tmpHandle_ = *numRes; |
690 | 0 | } else if (vmisa<JSString>(*tmpHandle_)) { |
691 | 0 | auto strRes = toString_RJS(runtime_, tmpHandle_); |
692 | 0 | if (LLVM_UNLIKELY(strRes == ExecutionStatus::EXCEPTION)) { |
693 | 0 | return ExecutionStatus::EXCEPTION; |
694 | 0 | } |
695 | 0 | tmpHandle_ = strRes->getHermesValue(); |
696 | 0 | } |
697 | 0 | if (tmpHandle_->isNumber()) { |
698 | 0 | auto intRes = toIntegerOrInfinity(runtime_, tmpHandle_); |
699 | 0 | assert( |
700 | 0 | intRes != ExecutionStatus::EXCEPTION && |
701 | 0 | "toInteger on a number cannot throw"); |
702 | | // Clamp result to [0,10]. |
703 | 0 | auto spaceCount = |
704 | 0 | static_cast<int>(std::max(0.0, std::min(10.0, intRes->getNumber()))); |
705 | 0 | if (spaceCount > 0) { |
706 | | // Construct a string with spaceCount spaces. |
707 | 0 | llvh::SmallString<32> spaces; |
708 | 0 | for (int i = 0; i < spaceCount; ++i) { |
709 | 0 | spaces.push_back(' '); |
710 | 0 | } |
711 | 0 | auto strRes = StringPrimitive::create(runtime_, spaces); |
712 | 0 | if (LLVM_UNLIKELY(strRes == ExecutionStatus::EXCEPTION)) { |
713 | 0 | return ExecutionStatus::EXCEPTION; |
714 | 0 | } |
715 | 0 | gap_ = strRes->getString(); |
716 | 0 | } |
717 | 0 | } else if (auto str = Handle<StringPrimitive>::dyn_vmcast(tmpHandle_)) { |
718 | 0 | if (str->getStringLength() > 10) { |
719 | 0 | auto strRes = StringPrimitive::slice(runtime_, str, 0, 10); |
720 | 0 | if (LLVM_UNLIKELY(strRes == ExecutionStatus::EXCEPTION)) { |
721 | 0 | return ExecutionStatus::EXCEPTION; |
722 | 0 | } |
723 | 0 | gap_ = strRes->getString(); |
724 | 0 | } else if (str->getStringLength() > 0) { |
725 | | // If the string is empty, we don't set the gap. |
726 | 0 | gap_ = str.get(); |
727 | 0 | } |
728 | 0 | } |
729 | 0 | return ExecutionStatus::RETURNED; |
730 | 0 | } |
731 | | |
732 | 0 | CallResult<bool> JSONStringifyer::operationStr(HermesValue key) { |
733 | 0 | GCScopeMarkerRAII marker{runtime_}; |
734 | 0 | tmpHandle_ = key; |
735 | | |
736 | | // Str.1: access holder[key]. |
737 | 0 | auto propRes = |
738 | 0 | JSObject::getComputed_RJS(operationStrHolder_, runtime_, tmpHandle_); |
739 | 0 | if (LLVM_UNLIKELY(propRes == ExecutionStatus::EXCEPTION)) { |
740 | 0 | return ExecutionStatus::EXCEPTION; |
741 | 0 | } |
742 | 0 | operationStrValue_.set(propRes->get()); |
743 | | |
744 | | // Str.2. If Type(value) is Object or BigInt, then |
745 | 0 | MutableHandle<> hValueHV{runtime_, *operationStrValue_}; |
746 | 0 | if (vmisa<BigIntPrimitive>(*operationStrValue_)) { |
747 | 0 | CallResult<HermesValue> hObjRes = toObject(runtime_, hValueHV); |
748 | 0 | if (LLVM_UNLIKELY(hObjRes == ExecutionStatus::EXCEPTION)) { |
749 | 0 | return ExecutionStatus::EXCEPTION; |
750 | 0 | } |
751 | 0 | assert(vmisa<JSBigInt>(*hObjRes) && "if not boxed bigint, then what?!"); |
752 | 0 | assert(vmisa<JSObject>(*hObjRes) && "if not object, then what?!"); |
753 | 0 | hValueHV = std::move(*hObjRes); |
754 | 0 | } |
755 | | |
756 | 0 | if (auto valueObj = Handle<JSObject>::dyn_vmcast(hValueHV)) { |
757 | | // Str.2. |
758 | | // Str.2.a: check if toJSON exists in value. |
759 | 0 | if (LLVM_UNLIKELY( |
760 | 0 | (propRes = JSObject::getNamedWithReceiver_RJS( |
761 | 0 | valueObj, |
762 | 0 | runtime_, |
763 | 0 | Predefined::getSymbolID(Predefined::toJSON), |
764 | 0 | operationStrValue_)) == ExecutionStatus::EXCEPTION)) { |
765 | 0 | return ExecutionStatus::EXCEPTION; |
766 | 0 | } |
767 | | // Str.2.b: check if toJSON is a Callable. |
768 | 0 | if (auto toJSON = Handle<Callable>::dyn_vmcast( |
769 | 0 | runtime_.makeHandle(std::move(*propRes)))) { |
770 | 0 | if (!tmpHandle_->isString()) { |
771 | | // Lazily convert key to a string. |
772 | 0 | auto status = toString_RJS(runtime_, tmpHandle_); |
773 | 0 | assert( |
774 | 0 | status != ExecutionStatus::EXCEPTION && |
775 | 0 | "toString on a property cannot fail"); |
776 | 0 | tmpHandle_ = status->getHermesValue(); |
777 | 0 | } |
778 | | // Call toJSON with key as argument, value as this. |
779 | 0 | auto callRes = Callable::executeCall1( |
780 | 0 | toJSON, runtime_, operationStrValue_, *tmpHandle_); |
781 | 0 | if (LLVM_UNLIKELY(callRes == ExecutionStatus::EXCEPTION)) { |
782 | 0 | return ExecutionStatus::EXCEPTION; |
783 | 0 | } |
784 | 0 | operationStrValue_ = std::move(*callRes); |
785 | 0 | } |
786 | 0 | } |
787 | | |
788 | | // Str.3. |
789 | 0 | if (replacerFunction_) { |
790 | | // Str.3.a. |
791 | 0 | if (!tmpHandle_->isString()) { |
792 | | // Lazily convert key to a string. |
793 | 0 | auto status = toString_RJS(runtime_, tmpHandle_); |
794 | 0 | assert( |
795 | 0 | status != ExecutionStatus::EXCEPTION && |
796 | 0 | "toString on a property cannot fail"); |
797 | 0 | tmpHandle_ = status->getHermesValue(); |
798 | 0 | } |
799 | | // If ReplacerFunction exists, call it with key and value as argument, |
800 | | // holder as this. |
801 | 0 | auto callRes = Callable::executeCall2( |
802 | 0 | replacerFunction_, |
803 | 0 | runtime_, |
804 | 0 | operationStrHolder_, |
805 | 0 | *tmpHandle_, |
806 | 0 | *operationStrValue_); |
807 | 0 | if (LLVM_UNLIKELY(callRes == ExecutionStatus::EXCEPTION)) { |
808 | 0 | return ExecutionStatus::EXCEPTION; |
809 | 0 | } |
810 | 0 | operationStrValue_ = std::move(*callRes); |
811 | 0 | } |
812 | | |
813 | | // Str.4: unbox value if necessary. |
814 | | // If Type(value) is Object, then |
815 | 0 | if (vmisa<JSNumber>(*operationStrValue_)) { |
816 | | // If value has a [[NumberData]] internal slot, then |
817 | | // Set value to ? ToNumber(value). |
818 | 0 | auto numRes = toNumber_RJS(runtime_, operationStrValue_); |
819 | 0 | if (LLVM_UNLIKELY(numRes == ExecutionStatus::EXCEPTION)) { |
820 | 0 | return ExecutionStatus::EXCEPTION; |
821 | 0 | } |
822 | 0 | operationStrValue_ = *numRes; |
823 | 0 | } else if (vmisa<JSString>(*operationStrValue_)) { |
824 | | // Else if value has a [[StringData]] internal slot, then |
825 | | // Set value to ? ToString(value). |
826 | 0 | auto strRes = toString_RJS(runtime_, operationStrValue_); |
827 | 0 | if (LLVM_UNLIKELY(strRes == ExecutionStatus::EXCEPTION)) { |
828 | 0 | return ExecutionStatus::EXCEPTION; |
829 | 0 | } |
830 | 0 | operationStrValue_ = strRes->getHermesValue(); |
831 | 0 | } else if (auto *jsBool = dyn_vmcast<JSBoolean>(*operationStrValue_)) { |
832 | | // Else if value has a [[BooleanData]] internal slot, then |
833 | | // Set value to value.[[BooleanData]]. |
834 | 0 | operationStrValue_ = |
835 | 0 | HermesValue::encodeBoolValue(jsBool->getPrimitiveBoolean()); |
836 | 0 | } else if (auto jsBigInt = dyn_vmcast<JSBigInt>(*operationStrValue_)) { |
837 | | // Else if value has a [[BigIntData]] internal slot, then |
838 | | // Set value to value.[[BigIntData]] |
839 | 0 | BigIntPrimitive *bigintData = |
840 | 0 | JSBigInt::getPrimitiveBigInt(jsBigInt, runtime_); |
841 | 0 | operationStrValue_ = HermesValue::encodeBigIntValue(bigintData); |
842 | 0 | } |
843 | | |
844 | | // Str.5. |
845 | 0 | if (operationStrValue_->isNull()) { |
846 | 0 | appendToOutput(Predefined::getSymbolID(Predefined::null)); |
847 | 0 | return true; |
848 | 0 | } |
849 | | |
850 | 0 | if (operationStrValue_->isBool()) { |
851 | 0 | if (operationStrValue_->getBool()) { |
852 | | // Str.6. |
853 | 0 | appendToOutput(Predefined::getSymbolID(Predefined::trueStr)); |
854 | 0 | } else { |
855 | | // Str.7. |
856 | 0 | appendToOutput(Predefined::getSymbolID(Predefined::falseStr)); |
857 | 0 | } |
858 | 0 | return true; |
859 | 0 | } |
860 | | |
861 | | // Str.8. |
862 | 0 | if (operationStrValue_->isString()) { |
863 | 0 | operationQuote( |
864 | 0 | StringPrimitive::createStringView( |
865 | 0 | runtime_, Handle<StringPrimitive>::vmcast(operationStrValue_))); |
866 | 0 | return true; |
867 | 0 | } |
868 | | |
869 | | // Str.9. |
870 | 0 | if (operationStrValue_->isNumber()) { |
871 | 0 | if (std::isfinite(operationStrValue_->getNumber())) { |
872 | 0 | auto status = toString_RJS(runtime_, operationStrValue_); |
873 | 0 | assert( |
874 | 0 | status != ExecutionStatus::EXCEPTION && |
875 | 0 | "toString on a number cannot fail"); |
876 | 0 | appendToOutput(status->get()); |
877 | 0 | } else { |
878 | 0 | appendToOutput(Predefined::getSymbolID(Predefined::null)); |
879 | 0 | } |
880 | 0 | return true; |
881 | 0 | } |
882 | | |
883 | | // Str.10 |
884 | 0 | if (vmisa<BigIntPrimitive>(*operationStrValue_)) { |
885 | 0 | return runtime_.raiseTypeError("Do not know how to serialize a BigInt"); |
886 | 0 | } |
887 | | |
888 | | // Str.11. |
889 | 0 | if (vmisa<JSObject>(*operationStrValue_) && |
890 | 0 | !vmisa<Callable>(*operationStrValue_)) { |
891 | 0 | auto cr = pushValueToStack(*operationStrValue_); |
892 | |
|
893 | 0 | if (cr == ExecutionStatus::EXCEPTION) { |
894 | 0 | return ExecutionStatus::EXCEPTION; |
895 | 0 | } |
896 | 0 | if (LLVM_UNLIKELY(!*cr)) { |
897 | 0 | return runtime_.raiseTypeError("cyclical structure in JSON object"); |
898 | 0 | } |
899 | | // Flush just before the recursive call (pushValueToStack can create |
900 | | // handles). |
901 | 0 | marker.flush(); |
902 | 0 | CallResult<bool> isArrayRes = |
903 | 0 | isArray(runtime_, vmcast<JSObject>(*operationStrValue_)); |
904 | 0 | if (LLVM_UNLIKELY(isArrayRes == ExecutionStatus::EXCEPTION)) { |
905 | 0 | return ExecutionStatus::EXCEPTION; |
906 | 0 | } |
907 | 0 | ExecutionStatus status = *isArrayRes ? operationJA() : operationJO(); |
908 | 0 | popValueFromStack(); |
909 | 0 | if (LLVM_UNLIKELY(status == ExecutionStatus::EXCEPTION)) { |
910 | 0 | return ExecutionStatus::EXCEPTION; |
911 | 0 | } |
912 | 0 | return true; |
913 | 0 | } |
914 | | |
915 | | // Str.12. |
916 | 0 | return false; |
917 | 0 | } |
918 | | |
919 | 0 | void JSONStringifyer::operationQuote(StringView value) { |
920 | 0 | if (value.isASCII()) { |
921 | 0 | quoteStringForJSON( |
922 | 0 | output_, ASCIIRef{value.castToCharPtr(), value.length()}); |
923 | 0 | } else { |
924 | 0 | quoteStringForJSON( |
925 | 0 | output_, UTF16Ref{value.castToChar16Ptr(), value.length()}); |
926 | 0 | } |
927 | 0 | } |
928 | | |
929 | 0 | ExecutionStatus JSONStringifyer::operationJA() { |
930 | 0 | GCScopeMarkerRAII marker{runtime_}; |
931 | | |
932 | | // JA.3. |
933 | 0 | auto stepBack = depthCount_; |
934 | | // JA.4. |
935 | 0 | if (depthCount_ + 1 >= MAX_RECURSION_DEPTH) { |
936 | 0 | return runtime_.raiseStackOverflow( |
937 | 0 | Runtime::StackOverflowKind::JSONStringify); |
938 | 0 | } |
939 | 0 | depthCount_++; |
940 | 0 | output_.push_back(u'['); |
941 | 0 | CallResult<uint64_t> lenRes = getArrayLikeLength_RJS( |
942 | 0 | runtime_.makeHandle( |
943 | 0 | vmcast<JSObject>( |
944 | 0 | stackValue_->at(stackValue_->size() - 1).getObject(runtime_))), |
945 | 0 | runtime_); |
946 | 0 | if (LLVM_UNLIKELY(lenRes == ExecutionStatus::EXCEPTION)) { |
947 | 0 | return ExecutionStatus::EXCEPTION; |
948 | 0 | } |
949 | 0 | if (*lenRes > 0) { |
950 | | // If array is not empty, we need to lead with an indent. |
951 | 0 | indent(); |
952 | 0 | } |
953 | | // JA.5, 6, 7, 8. |
954 | 0 | for (uint64_t index = 0; index < *lenRes; ++index) { |
955 | 0 | if (index > 0) { |
956 | | // JA.10. |
957 | 0 | output_.push_back(u','); |
958 | 0 | indent(); |
959 | 0 | } |
960 | | // JA.8.a. |
961 | 0 | operationStrHolder_ = vmcast<JSObject>( |
962 | 0 | stackValue_->at(stackValue_->size() - 1).getObject(runtime_)); |
963 | | // Flush just before the recursion in case any handles were created. |
964 | 0 | marker.flush(); |
965 | 0 | auto status = operationStr(HermesValue::encodeUntrustedNumberValue(index)); |
966 | 0 | if (LLVM_UNLIKELY(status == ExecutionStatus::EXCEPTION)) { |
967 | 0 | return ExecutionStatus::EXCEPTION; |
968 | 0 | } |
969 | 0 | if (LLVM_UNLIKELY(!status.getValue())) { |
970 | | // operationStr returns undefined, we need to replace with null. |
971 | 0 | appendToOutput(Predefined::getSymbolID(Predefined::null)); |
972 | 0 | } |
973 | 0 | } |
974 | 0 | depthCount_ = stepBack; |
975 | |
|
976 | 0 | if (*lenRes > 0) { |
977 | 0 | indent(); |
978 | 0 | } |
979 | 0 | output_.push_back(u']'); |
980 | 0 | return ExecutionStatus::RETURNED; |
981 | 0 | } |
982 | | |
983 | 0 | ExecutionStatus JSONStringifyer::operationJO() { |
984 | 0 | GCScopeMarkerRAII marker{runtime_}; |
985 | | |
986 | | // JO.3. |
987 | 0 | auto stepBack = depthCount_; |
988 | | // JO.4. |
989 | 0 | if (depthCount_ + 1 >= MAX_RECURSION_DEPTH) { |
990 | 0 | return runtime_.raiseStackOverflow( |
991 | 0 | Runtime::StackOverflowKind::JSONStringify); |
992 | 0 | } |
993 | 0 | depthCount_++; |
994 | 0 | output_.push_back(u'{'); |
995 | 0 | auto beginningLoc = output_.size(); |
996 | 0 | indent(); |
997 | |
|
998 | 0 | if (propertyList_) { |
999 | | // JO.5. |
1000 | 0 | operationJOK_ = propertyList_.get(); |
1001 | 0 | } else { |
1002 | | // JO.6. |
1003 | 0 | tmpHandle_ = HermesValue::encodeObjectValue( |
1004 | 0 | stackValue_->at(stackValue_->size() - 1).getObject(runtime_)); |
1005 | 0 | if (LLVM_LIKELY(!Handle<JSObject>::vmcast(tmpHandle_)->isProxyObject())) { |
1006 | | // enumerableOwnProperties_RJS is the spec definition, and is |
1007 | | // used below on proxies so the correct traps get called. In |
1008 | | // the common case of a non-proxy object, we can do less work by |
1009 | | // calling getOwnPropertyNames. |
1010 | 0 | auto cr = JSObject::getOwnPropertyNames( |
1011 | 0 | Handle<JSObject>::vmcast(tmpHandle_), runtime_, true); |
1012 | 0 | if (cr == ExecutionStatus::EXCEPTION) { |
1013 | 0 | return ExecutionStatus::EXCEPTION; |
1014 | 0 | } |
1015 | 0 | operationJOK_ = **cr; |
1016 | 0 | } else { |
1017 | 0 | CallResult<HermesValue> ownPropRes = enumerableOwnProperties_RJS( |
1018 | 0 | runtime_, |
1019 | 0 | Handle<JSObject>::vmcast(tmpHandle_), |
1020 | 0 | EnumerableOwnPropertiesKind::Key); |
1021 | 0 | if (ownPropRes == ExecutionStatus::EXCEPTION) { |
1022 | 0 | return ExecutionStatus::EXCEPTION; |
1023 | 0 | } |
1024 | 0 | operationJOK_ = vmcast<JSArray>(*ownPropRes); |
1025 | 0 | } |
1026 | 0 | } |
1027 | | |
1028 | 0 | marker.flush(); |
1029 | | |
1030 | | // JO.8. |
1031 | 0 | bool hasElement = false; |
1032 | 0 | for (uint32_t index = 0, len = operationJOK_->getEndIndex(); index < len; |
1033 | 0 | ++index) { |
1034 | | // JO.8.a. |
1035 | | // We are speculating that the Str operation will not return undefined, |
1036 | | // and just append the key/value pair to the output. If it turns out |
1037 | | // that the Str operation does return undefined, we roll back to |
1038 | | // curLocation. |
1039 | 0 | auto savedLocation = output_.size(); |
1040 | |
|
1041 | 0 | if (hasElement) { |
1042 | | // JO.10. |
1043 | 0 | output_.push_back(u','); |
1044 | 0 | indent(); |
1045 | 0 | } |
1046 | |
|
1047 | 0 | tmpHandle_ = operationJOK_->at(runtime_, index).unboxToHV(runtime_); |
1048 | 0 | if (LLVM_UNLIKELY(!tmpHandle_->isString())) { |
1049 | | // property may come from getOwnPropertyNames, which may contain numbers. |
1050 | | // getOwnPropertyNames and propertyList_ are both only populated |
1051 | | // with strings, numbers, and undefined only. |
1052 | | // None of them are objects, so toString cannot throw. |
1053 | 0 | assert(!tmpHandle_->isObject() && "property name is an object"); |
1054 | 0 | auto status = toString_RJS(runtime_, tmpHandle_); |
1055 | 0 | assert( |
1056 | 0 | status != ExecutionStatus::EXCEPTION && |
1057 | 0 | "toString on a property cannot fail"); |
1058 | 0 | tmpHandle_ = status->getHermesValue(); |
1059 | 0 | } |
1060 | | // tmpHandle now contains property as string. |
1061 | | // JO.8.b.i |
1062 | 0 | operationQuote( |
1063 | 0 | StringPrimitive::createStringView( |
1064 | 0 | runtime_, Handle<StringPrimitive>::vmcast(tmpHandle_))); |
1065 | | // JO.8.b.ii |
1066 | 0 | output_.push_back(u':'); |
1067 | | // JO.8.b.iii |
1068 | 0 | if (gap_.get()) { |
1069 | 0 | output_.push_back(u' '); |
1070 | 0 | } |
1071 | | |
1072 | | // JO.9.a. |
1073 | 0 | operationStrHolder_ = vmcast<JSObject>( |
1074 | 0 | stackValue_->at(stackValue_->size() - 1).getObject(runtime_)); |
1075 | |
|
1076 | 0 | tmpHandle2_ = operationJOK_.getHermesValue(); |
1077 | 0 | if (PropStorage::push_back(stackJO_, runtime_, tmpHandle2_) == |
1078 | 0 | ExecutionStatus::EXCEPTION) { |
1079 | 0 | return ExecutionStatus::EXCEPTION; |
1080 | 0 | } |
1081 | | |
1082 | | // Flush just before recursion (propStoragePushBack may create handles). |
1083 | 0 | marker.flush(); |
1084 | 0 | auto result = operationStr(*tmpHandle_); |
1085 | |
|
1086 | 0 | operationJOK_ = |
1087 | 0 | vmcast<JSArray>(stackJO_->pop_back(runtime_).getObject(runtime_)); |
1088 | |
|
1089 | 0 | if (LLVM_UNLIKELY(result == ExecutionStatus::EXCEPTION)) { |
1090 | 0 | return ExecutionStatus::EXCEPTION; |
1091 | 0 | } |
1092 | | |
1093 | 0 | if (LLVM_UNLIKELY(!result.getValue())) { |
1094 | | // Str returns undefined, we need to roll back. |
1095 | 0 | output_.resize(savedLocation); |
1096 | 0 | } else { |
1097 | 0 | hasElement = true; |
1098 | 0 | } |
1099 | 0 | } |
1100 | | // It's important to reset depthCount_ first, because the last |
1101 | | // indent before } should be the old indent. |
1102 | 0 | depthCount_ = stepBack; |
1103 | |
|
1104 | 0 | if (hasElement) { |
1105 | 0 | indent(); |
1106 | 0 | } else { |
1107 | | // If the object is empty, we need to roll back the first indent. |
1108 | 0 | output_.resize(beginningLoc); |
1109 | 0 | } |
1110 | 0 | output_.push_back(u'}'); |
1111 | 0 | return ExecutionStatus::RETURNED; |
1112 | 0 | } |
1113 | | |
1114 | 0 | void JSONStringifyer::indent() { |
1115 | 0 | if (gap_.get()) { |
1116 | 0 | output_.push_back(u'\n'); |
1117 | 0 | for (uint32_t i = 0; i < depthCount_; ++i) { |
1118 | 0 | appendToOutput(gap_.get()); |
1119 | 0 | } |
1120 | 0 | } |
1121 | 0 | } |
1122 | | |
1123 | 0 | CallResult<bool> JSONStringifyer::pushValueToStack(HermesValue value) { |
1124 | 0 | assert(vmisa<JSObject>(value) && "Can only push object to stack"); |
1125 | | |
1126 | 0 | for (uint32_t i = 0, len = stackValue_->size(); i < len; ++i) { |
1127 | 0 | if (stackValue_->at(i).getObject(runtime_) == value.getObject()) { |
1128 | 0 | return false; |
1129 | 0 | } |
1130 | 0 | } |
1131 | | |
1132 | 0 | tmpHandle_ = value; |
1133 | 0 | if (PropStorage::push_back(stackValue_, runtime_, tmpHandle_) == |
1134 | 0 | ExecutionStatus::EXCEPTION) { |
1135 | 0 | return ExecutionStatus::EXCEPTION; |
1136 | 0 | } |
1137 | 0 | return true; |
1138 | 0 | } |
1139 | | |
1140 | 0 | void JSONStringifyer::popValueFromStack() { |
1141 | 0 | assert(stackValue_->size() && "Cannot pop from an empty stack"); |
1142 | 0 | stackValue_->pop_back(runtime_); |
1143 | 0 | } |
1144 | | |
1145 | 0 | void JSONStringifyer::appendToOutput(SymbolID identifierID) { |
1146 | 0 | appendToOutput(runtime_.getStringPrimFromSymbolID(identifierID)); |
1147 | 0 | } |
1148 | | |
1149 | 0 | void JSONStringifyer::appendToOutput(const StringPrimitive *str) { |
1150 | 0 | str->appendUTF16String(output_); |
1151 | 0 | } |
1152 | | |
1153 | 0 | CallResult<HermesValue> JSONStringifyer::stringify(Handle<> value) { |
1154 | | // All previous steps have been covered by the constructor. |
1155 | | // Clear the output buffer. |
1156 | 0 | output_.clear(); |
1157 | | |
1158 | | // Step 9, 10 in ES5.1 15.12.3. |
1159 | 0 | operationStrHolder_ = JSObject::create(runtime_).get(); |
1160 | 0 | auto status = JSObject::defineOwnProperty( |
1161 | 0 | operationStrHolder_, |
1162 | 0 | runtime_, |
1163 | 0 | Predefined::getSymbolID(Predefined::emptyString), |
1164 | 0 | DefinePropertyFlags::getDefaultNewPropertyFlags(), |
1165 | 0 | value); |
1166 | 0 | assert( |
1167 | 0 | status != ExecutionStatus::EXCEPTION && *status && |
1168 | 0 | "defineOwnProperty on a newly created object cannot fail"); |
1169 | 0 | (void)status; |
1170 | | |
1171 | | // Step 11 in ES5.1 15.12.3. |
1172 | 0 | status = operationStr( |
1173 | 0 | HermesValue::encodeStringValue( |
1174 | 0 | runtime_.getPredefinedString(Predefined::emptyString))); |
1175 | 0 | if (LLVM_UNLIKELY(status == ExecutionStatus::EXCEPTION)) { |
1176 | 0 | return ExecutionStatus::EXCEPTION; |
1177 | 0 | } |
1178 | 0 | if (status.getValue()) { |
1179 | 0 | return StringPrimitive::create(runtime_, output_); |
1180 | 0 | } else { |
1181 | 0 | return HermesValue::encodeUndefinedValue(); |
1182 | 0 | } |
1183 | 0 | } |
1184 | | |
1185 | | CallResult<HermesValue> runtimeJSONStringify( |
1186 | | Runtime &runtime, |
1187 | | Handle<> value, |
1188 | | Handle<> replacer, |
1189 | 0 | Handle<> space) { |
1190 | 0 | GCScope gcScope{runtime, "runtimeJSONStringify"}; |
1191 | |
|
1192 | 0 | JSONStringifyer stringifyer{runtime}; |
1193 | 0 | if (stringifyer.init(replacer, space) == ExecutionStatus::EXCEPTION) { |
1194 | 0 | return ExecutionStatus::EXCEPTION; |
1195 | 0 | } |
1196 | 0 | return stringifyer.stringify(value); |
1197 | 0 | } |
1198 | | |
1199 | | } // namespace vm |
1200 | | } // namespace hermes |