Coverage Report

Created: 2025-12-12 07:27

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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