tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

ToSource.cpp (6960B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
      2 * vim: set ts=8 sts=2 et sw=2 tw=80:
      3 * This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "vm/ToSource.h"
      8 
      9 #include "mozilla/Assertions.h"     // MOZ_ASSERT
     10 #include "mozilla/FloatingPoint.h"  // mozilla::IsNegativeZero
     11 
     12 #include <stdint.h>  // uint32_t
     13 
     14 #include "builtin/Array.h"          // ArrayToSource
     15 #include "builtin/Boolean.h"        // BooleanToString
     16 #include "builtin/Object.h"         // ObjectToSource
     17 #include "gc/GCEnum.h"              // CanGC
     18 #include "js/Class.h"               // ESClass
     19 #include "js/friend/StackLimits.h"  // js::AutoCheckRecursionLimit
     20 #include "js/Object.h"              // JS::GetBuiltinClass
     21 #include "js/Printer.h"             // QuoteString
     22 #include "js/Symbol.h"              // SymbolCode, JS::WellKnownSymbolLimit
     23 #include "js/TypeDecls.h"  // Rooted{Object, String, Value}, HandleValue, Latin1Char
     24 #include "js/Utility.h"               // UniqueChars
     25 #include "js/Value.h"                 // JS::Value
     26 #include "util/StringBuilder.h"       // JSStringBuilder
     27 #include "vm/ErrorObject.h"           // ErrorObject, ErrorToSource
     28 #include "vm/Interpreter.h"           // Call
     29 #include "vm/JSContext.h"             // JSContext
     30 #include "vm/JSFunction.h"            // JSFunction, fun_toStringHelper
     31 #include "vm/SelfHosting.h"           // CallSelfHostedFunction
     32 #include "vm/Stack.h"                 // FixedInvokeArgs
     33 #include "vm/StaticStrings.h"         // StaticStrings
     34 #include "vm/StringType.h"            // NewStringCopy{N,Z}, ToString
     35 #include "vm/SymbolType.h"            // Symbol
     36 #include "vm/JSContext-inl.h"         // JSContext::check
     37 #include "vm/JSObject-inl.h"          // IsCallable
     38 #include "vm/ObjectOperations-inl.h"  // GetProperty
     39 
     40 using namespace js;
     41 
     42 using mozilla::IsNegativeZero;
     43 
     44 using JS::GetBuiltinClass;
     45 
     46 /*
     47 * Convert a JSString to its source expression; returns null after reporting an
     48 * error, otherwise returns a new string reference. No Handle needed since the
     49 * input is dead after the GC.
     50 */
     51 static JSString* StringToSource(JSContext* cx, JSString* str) {
     52  UniqueChars chars = QuoteString(cx, str, '"');
     53  if (!chars) {
     54    return nullptr;
     55  }
     56  return NewStringCopyZ<CanGC>(cx, chars.get());
     57 }
     58 
     59 static JSString* SymbolToSource(JSContext* cx, JS::Symbol* symbol) {
     60  using JS::SymbolCode;
     61 
     62  RootedString desc(cx, symbol->description());
     63  SymbolCode code = symbol->code();
     64  if (symbol->isWellKnownSymbol()) {
     65    // Well-known symbol.
     66    return desc;
     67  }
     68 
     69  if (code == SymbolCode::PrivateNameSymbol) {
     70    MOZ_ASSERT(desc);
     71    return desc;
     72  }
     73 
     74  MOZ_ASSERT(code == SymbolCode::InSymbolRegistry ||
     75             code == SymbolCode::UniqueSymbol);
     76 
     77  JSStringBuilder buf(cx);
     78  if (code == SymbolCode::InSymbolRegistry ? !buf.append("Symbol.for(")
     79                                           : !buf.append("Symbol(")) {
     80    return nullptr;
     81  }
     82  if (desc) {
     83    UniqueChars quoted = QuoteString(cx, desc, '"');
     84    if (!quoted || !buf.append(quoted.get(), strlen(quoted.get()))) {
     85      return nullptr;
     86    }
     87  }
     88  if (!buf.append(')')) {
     89    return nullptr;
     90  }
     91  return buf.finishString();
     92 }
     93 
     94 static JSString* BoxedToSource(JSContext* cx, HandleObject obj,
     95                               const char* constructor) {
     96  RootedValue value(cx);
     97  if (!Unbox(cx, obj, &value)) {
     98    return nullptr;
     99  }
    100  MOZ_ASSERT(!value.isUndefined());
    101 
    102  RootedString str(cx, ValueToSource(cx, value));
    103  if (!str) {
    104    return nullptr;
    105  }
    106 
    107  JSStringBuilder buf(cx);
    108  if (!buf.append("new ") || !buf.append(constructor, strlen(constructor)) ||
    109      !buf.append('(') || !buf.append(str) || !buf.append(')')) {
    110    return nullptr;
    111  }
    112 
    113  return buf.finishString();
    114 }
    115 
    116 JSString* js::ValueToSource(JSContext* cx, HandleValue v) {
    117  AutoCheckRecursionLimit recursion(cx);
    118  if (!recursion.check(cx)) {
    119    return nullptr;
    120  }
    121  cx->check(v);
    122 
    123  switch (v.type()) {
    124    case JS::ValueType::Undefined:
    125      return cx->names().void_0_;
    126 
    127    case JS::ValueType::String:
    128      return StringToSource(cx, v.toString());
    129 
    130    case JS::ValueType::Symbol:
    131      return SymbolToSource(cx, v.toSymbol());
    132 
    133    case JS::ValueType::Null:
    134      return cx->names().null;
    135 
    136    case JS::ValueType::Boolean:
    137      return BooleanToString(cx, v.toBoolean());
    138 
    139    case JS::ValueType::Double:
    140      /* Special case to preserve negative zero, _contra_ toString. */
    141      if (IsNegativeZero(v.toDouble())) {
    142        static const Latin1Char negativeZero[] = {'-', '0'};
    143 
    144        return NewStringCopyN<CanGC>(cx, negativeZero, std::size(negativeZero));
    145      }
    146      [[fallthrough]];
    147    case JS::ValueType::Int32:
    148      return ToString<CanGC>(cx, v);
    149 
    150    case JS::ValueType::BigInt: {
    151      RootedString str(cx, ToString<CanGC>(cx, v));
    152      if (!str) {
    153        return nullptr;
    154      }
    155 
    156      RootedString n(cx, cx->staticStrings().getUnit('n'));
    157 
    158      return ConcatStrings<CanGC>(cx, str, n);
    159    }
    160 
    161    case JS::ValueType::Object: {
    162      RootedValue fval(cx);
    163      RootedObject obj(cx, &v.toObject());
    164      if (!GetProperty(cx, obj, obj, cx->names().toSource, &fval)) {
    165        return nullptr;
    166      }
    167      if (IsCallable(fval)) {
    168        RootedValue v(cx);
    169        if (!js::Call(cx, fval, obj, &v)) {
    170          return nullptr;
    171        }
    172 
    173        return ToString<CanGC>(cx, v);
    174      }
    175 
    176      ESClass cls;
    177      if (!GetBuiltinClass(cx, obj, &cls)) {
    178        return nullptr;
    179      }
    180 
    181      // All ToSource functions must be able to handle wrapped objects!
    182      switch (cls) {
    183        case ESClass::Function:
    184          return fun_toStringHelper(cx, obj, true);
    185 
    186        case ESClass::Array:
    187          return ArrayToSource(cx, obj);
    188 
    189        case ESClass::Error:
    190          return ErrorToSource(cx, obj);
    191 
    192        case ESClass::RegExp: {
    193          FixedInvokeArgs<0> args(cx);
    194          RootedValue rval(cx);
    195          if (!CallSelfHostedFunction(cx, cx->names().dollar_RegExpToString_, v,
    196                                      args, &rval)) {
    197            return nullptr;
    198          }
    199          return ToString<CanGC>(cx, rval);
    200        }
    201 
    202        case ESClass::Boolean:
    203          return BoxedToSource(cx, obj, "Boolean");
    204 
    205        case ESClass::Number:
    206          return BoxedToSource(cx, obj, "Number");
    207 
    208        case ESClass::String:
    209          return BoxedToSource(cx, obj, "String");
    210 
    211        case ESClass::Date:
    212          return BoxedToSource(cx, obj, "Date");
    213 
    214        default:
    215          return ObjectToSource(cx, obj);
    216      }
    217    }
    218 
    219    case JS::ValueType::PrivateGCThing:
    220    case JS::ValueType::Magic:
    221      MOZ_ASSERT_UNREACHABLE(
    222          "internal value types shouldn't leak into places "
    223          "wanting source representations");
    224      return nullptr;
    225  }
    226 
    227  MOZ_ASSERT_UNREACHABLE("shouldn't see an unrecognized value type");
    228  return nullptr;
    229 }