tor-browser

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

UbiNode.cpp (16030B)


      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 "js/UbiNode.h"
      8 
      9 #include "mozilla/Assertions.h"
     10 
     11 #include <algorithm>
     12 
     13 #include "debugger/Debugger.h"
     14 #include "gc/GC.h"
     15 #include "jit/JitCode.h"
     16 #include "js/Debug.h"
     17 #include "js/TracingAPI.h"
     18 #include "js/TypeDecls.h"
     19 #include "js/UbiNodeUtils.h"
     20 #include "js/Utility.h"
     21 #include "util/Text.h"
     22 #include "vm/BigIntType.h"
     23 #include "vm/Compartment.h"
     24 #include "vm/EnvironmentObject.h"
     25 #include "vm/GetterSetter.h"
     26 #include "vm/GlobalObject.h"
     27 #include "vm/JSContext.h"
     28 #include "vm/JSObject.h"
     29 #include "vm/JSScript.h"
     30 #include "vm/PropMap.h"
     31 #include "vm/Scope.h"
     32 #include "vm/Shape.h"
     33 #include "vm/StringType.h"
     34 #include "vm/SymbolType.h"
     35 
     36 #include "debugger/Debugger-inl.h"
     37 #include "gc/StableCellHasher-inl.h"
     38 #include "vm/JSObject-inl.h"
     39 
     40 using namespace js;
     41 
     42 using JS::ApplyGCThingTyped;
     43 using JS::HandleValue;
     44 using JS::Value;
     45 using JS::ZoneSet;
     46 using JS::ubi::AtomOrTwoByteChars;
     47 using JS::ubi::CoarseType;
     48 using JS::ubi::Concrete;
     49 using JS::ubi::Edge;
     50 using JS::ubi::EdgeRange;
     51 using JS::ubi::EdgeVector;
     52 using JS::ubi::Node;
     53 using JS::ubi::StackFrame;
     54 using JS::ubi::TracerConcrete;
     55 using JS::ubi::TracerConcreteWithRealm;
     56 using mozilla::RangedPtr;
     57 
     58 struct CopyToBufferMatcher {
     59  RangedPtr<char16_t> destination;
     60  size_t maxLength;
     61 
     62  CopyToBufferMatcher(RangedPtr<char16_t> destination, size_t maxLength)
     63      : destination(destination), maxLength(maxLength) {}
     64 
     65  template <typename CharT>
     66  static size_t copyToBufferHelper(const CharT* src, RangedPtr<char16_t> dest,
     67                                   size_t length) {
     68    size_t i = 0;
     69    for (; i < length; i++) {
     70      dest[i] = src[i];
     71    }
     72    return i;
     73  }
     74 
     75  size_t operator()(JSAtom* atom) {
     76    if (!atom) {
     77      return 0;
     78    }
     79 
     80    size_t length = std::min(atom->length(), maxLength);
     81    JS::AutoCheckCannotGC noGC;
     82    return atom->hasTwoByteChars()
     83               ? copyToBufferHelper(atom->twoByteChars(noGC), destination,
     84                                    length)
     85               : copyToBufferHelper(atom->latin1Chars(noGC), destination,
     86                                    length);
     87  }
     88 
     89  size_t operator()(const char16_t* chars) {
     90    if (!chars) {
     91      return 0;
     92    }
     93 
     94    size_t length = std::min(js_strlen(chars), maxLength);
     95    return copyToBufferHelper(chars, destination, length);
     96  }
     97 };
     98 
     99 size_t JS::ubi::AtomOrTwoByteChars::copyToBuffer(
    100    RangedPtr<char16_t> destination, size_t length) {
    101  CopyToBufferMatcher m(destination, length);
    102  return match(m);
    103 }
    104 
    105 struct LengthMatcher {
    106  size_t operator()(JSAtom* atom) { return atom ? atom->length() : 0; }
    107 
    108  size_t operator()(const char16_t* chars) {
    109    return chars ? js_strlen(chars) : 0;
    110  }
    111 };
    112 
    113 size_t JS::ubi::AtomOrTwoByteChars::length() {
    114  LengthMatcher m;
    115  return match(m);
    116 }
    117 
    118 size_t StackFrame::source(RangedPtr<char16_t> destination,
    119                          size_t length) const {
    120  auto s = source();
    121  return s.copyToBuffer(destination, length);
    122 }
    123 
    124 size_t StackFrame::functionDisplayName(RangedPtr<char16_t> destination,
    125                                       size_t length) const {
    126  auto name = functionDisplayName();
    127  return name.copyToBuffer(destination, length);
    128 }
    129 
    130 size_t StackFrame::sourceLength() { return source().length(); }
    131 
    132 size_t StackFrame::functionDisplayNameLength() {
    133  return functionDisplayName().length();
    134 }
    135 
    136 // All operations on null ubi::Nodes crash.
    137 CoarseType Concrete<void>::coarseType() const { MOZ_CRASH("null ubi::Node"); }
    138 const char16_t* Concrete<void>::typeName() const {
    139  MOZ_CRASH("null ubi::Node");
    140 }
    141 JS::Zone* Concrete<void>::zone() const { MOZ_CRASH("null ubi::Node"); }
    142 JS::Compartment* Concrete<void>::compartment() const {
    143  MOZ_CRASH("null ubi::Node");
    144 }
    145 JS::Realm* Concrete<void>::realm() const { MOZ_CRASH("null ubi::Node"); }
    146 
    147 UniquePtr<EdgeRange> Concrete<void>::edges(JSContext*, bool) const {
    148  MOZ_CRASH("null ubi::Node");
    149 }
    150 
    151 Node::Size Concrete<void>::size(mozilla::MallocSizeOf mallocSizeof) const {
    152  MOZ_CRASH("null ubi::Node");
    153 }
    154 
    155 Node::Node(JS::GCCellPtr thing) {
    156  ApplyGCThingTyped(thing, [this](auto t) { this->construct(t); });
    157 }
    158 
    159 Node::Node(HandleValue value) {
    160  if (!ApplyGCThingTyped(value, [this](auto t) { this->construct(t); })) {
    161    construct<void>(nullptr);
    162  }
    163 }
    164 
    165 static bool IsSafeToExposeToJS(JSObject* obj) {
    166  if (obj->is<js::EnvironmentObject>() || obj->is<js::ScriptSourceObject>() ||
    167      obj->is<js::DebugEnvironmentProxy>()) {
    168    return false;
    169  }
    170  if (obj->is<JSFunction>() && js::IsInternalFunctionObject(*obj)) {
    171    return false;
    172  }
    173  return true;
    174 }
    175 
    176 Value Node::exposeToJS() const {
    177  Value v;
    178 
    179  if (is<JSObject>()) {
    180    JSObject* obj = as<JSObject>();
    181    if (IsSafeToExposeToJS(obj)) {
    182      v.setObject(*obj);
    183    } else {
    184      v.setUndefined();
    185    }
    186  } else if (is<JSString>()) {
    187    v.setString(as<JSString>());
    188  } else if (is<JS::Symbol>()) {
    189    v.setSymbol(as<JS::Symbol>());
    190  } else if (is<BigInt>()) {
    191    v.setBigInt(as<BigInt>());
    192  } else {
    193    v.setUndefined();
    194  }
    195 
    196  ExposeValueToActiveJS(v);
    197 
    198  return v;
    199 }
    200 
    201 // A JS::CallbackTracer subclass that adds a Edge to a Vector for each
    202 // edge on which it is invoked.
    203 class EdgeVectorTracer final : public JS::CallbackTracer {
    204  // The vector to which we add Edges.
    205  EdgeVector* vec;
    206 
    207  // True if we should populate the edge's names.
    208  bool wantNames;
    209 
    210  void onChild(JS::GCCellPtr thing, const char* name) override {
    211    if (!okay) {
    212      return;
    213    }
    214 
    215    // Don't trace permanent atoms and well-known symbols that are owned by
    216    // a parent JSRuntime.
    217    if (thing.is<JSString>() && thing.as<JSString>().isPermanentAtom()) {
    218      return;
    219    }
    220    if (thing.is<JS::Symbol>() && thing.as<JS::Symbol>().isWellKnownSymbol()) {
    221      return;
    222    }
    223 
    224    char16_t* name16 = nullptr;
    225    if (wantNames) {
    226      // Ask the tracer to compute an edge name for us.
    227      char buffer[1024];
    228      context().getEdgeName(name, buffer, sizeof(buffer));
    229      name = buffer;
    230 
    231      // Convert the name to char16_t characters.
    232      name16 = js_pod_malloc<char16_t>(strlen(name) + 1);
    233      if (!name16) {
    234        okay = false;
    235        return;
    236      }
    237 
    238      size_t i;
    239      for (i = 0; name[i]; i++) {
    240        name16[i] = name[i];
    241      }
    242      name16[i] = '\0';
    243    }
    244 
    245    // The simplest code is correct! The temporary Edge takes
    246    // ownership of name; if the append succeeds, the vector element
    247    // then takes ownership; if the append fails, then the temporary
    248    // retains it, and its destructor will free it.
    249    if (!vec->append(Edge(name16, Node(thing)))) {
    250      okay = false;
    251      return;
    252    }
    253  }
    254 
    255 public:
    256  // True if no errors (OOM, say) have yet occurred.
    257  bool okay;
    258 
    259  EdgeVectorTracer(JSRuntime* rt, EdgeVector* vec, bool wantNames)
    260      : JS::CallbackTracer(rt), vec(vec), wantNames(wantNames), okay(true) {}
    261 };
    262 
    263 template <typename Referent>
    264 JS::Zone* TracerConcrete<Referent>::zone() const {
    265  return get().zoneFromAnyThread();
    266 }
    267 
    268 template JS::Zone* TracerConcrete<js::BaseScript>::zone() const;
    269 template JS::Zone* TracerConcrete<js::Shape>::zone() const;
    270 template JS::Zone* TracerConcrete<js::BaseShape>::zone() const;
    271 template JS::Zone* TracerConcrete<js::GetterSetter>::zone() const;
    272 template JS::Zone* TracerConcrete<js::PropMap>::zone() const;
    273 template JS::Zone* TracerConcrete<js::RegExpShared>::zone() const;
    274 template JS::Zone* TracerConcrete<js::Scope>::zone() const;
    275 template JS::Zone* TracerConcrete<JS::Symbol>::zone() const;
    276 template JS::Zone* TracerConcrete<BigInt>::zone() const;
    277 template JS::Zone* TracerConcrete<JSString>::zone() const;
    278 
    279 template <typename Referent>
    280 UniquePtr<EdgeRange> TracerConcrete<Referent>::edges(JSContext* cx,
    281                                                     bool wantNames) const {
    282  auto range = js::MakeUnique<SimpleEdgeRange>();
    283  if (!range) {
    284    return nullptr;
    285  }
    286 
    287  if (!range->addTracerEdges(cx->runtime(), ptr,
    288                             JS::MapTypeToTraceKind<Referent>::kind,
    289                             wantNames)) {
    290    return nullptr;
    291  }
    292 
    293  // Note: Clang 3.8 (or older) require an explicit construction of the
    294  // target UniquePtr type. When we no longer require to support these Clang
    295  // versions the return statement can be simplified to |return range;|.
    296  return UniquePtr<EdgeRange>(range.release());
    297 }
    298 
    299 template UniquePtr<EdgeRange> TracerConcrete<js::BaseScript>::edges(
    300    JSContext* cx, bool wantNames) const;
    301 template UniquePtr<EdgeRange> TracerConcrete<js::Shape>::edges(
    302    JSContext* cx, bool wantNames) const;
    303 template UniquePtr<EdgeRange> TracerConcrete<js::BaseShape>::edges(
    304    JSContext* cx, bool wantNames) const;
    305 template UniquePtr<EdgeRange> TracerConcrete<js::GetterSetter>::edges(
    306    JSContext* cx, bool wantNames) const;
    307 template UniquePtr<EdgeRange> TracerConcrete<js::PropMap>::edges(
    308    JSContext* cx, bool wantNames) const;
    309 template UniquePtr<EdgeRange> TracerConcrete<js::RegExpShared>::edges(
    310    JSContext* cx, bool wantNames) const;
    311 template UniquePtr<EdgeRange> TracerConcrete<js::Scope>::edges(
    312    JSContext* cx, bool wantNames) const;
    313 template UniquePtr<EdgeRange> TracerConcrete<JS::Symbol>::edges(
    314    JSContext* cx, bool wantNames) const;
    315 template UniquePtr<EdgeRange> TracerConcrete<BigInt>::edges(
    316    JSContext* cx, bool wantNames) const;
    317 template UniquePtr<EdgeRange> TracerConcrete<JSString>::edges(
    318    JSContext* cx, bool wantNames) const;
    319 
    320 template <typename Referent>
    321 JS::Compartment* TracerConcreteWithRealm<Referent>::compartment() const {
    322  return TracerBase::get().compartment();
    323 }
    324 
    325 template <typename Referent>
    326 Realm* TracerConcreteWithRealm<Referent>::realm() const {
    327  return TracerBase::get().realm();
    328 }
    329 
    330 template Realm* TracerConcreteWithRealm<js::BaseScript>::realm() const;
    331 template JS::Compartment* TracerConcreteWithRealm<js::BaseScript>::compartment()
    332    const;
    333 
    334 bool Concrete<JSObject>::hasAllocationStack() const {
    335  return !!js::Debugger::getObjectAllocationSite(get());
    336 }
    337 
    338 StackFrame Concrete<JSObject>::allocationStack() const {
    339  MOZ_ASSERT(hasAllocationStack());
    340  return StackFrame(js::Debugger::getObjectAllocationSite(get()));
    341 }
    342 
    343 const char* Concrete<JSObject>::jsObjectClassName() const {
    344  return Concrete::get().getClass()->name;
    345 }
    346 
    347 JS::Compartment* Concrete<JSObject>::compartment() const {
    348  return Concrete::get().compartment();
    349 }
    350 
    351 Realm* Concrete<JSObject>::realm() const {
    352  // Cross-compartment wrappers are shared by all realms in the compartment,
    353  // so we return nullptr in that case.
    354  return JS::GetObjectRealmOrNull(&Concrete::get());
    355 }
    356 
    357 const char16_t Concrete<JS::Symbol>::concreteTypeName[] = u"JS::Symbol";
    358 const char16_t Concrete<BigInt>::concreteTypeName[] = u"JS::BigInt";
    359 const char16_t Concrete<js::BaseScript>::concreteTypeName[] = u"js::BaseScript";
    360 const char16_t Concrete<js::jit::JitCode>::concreteTypeName[] =
    361    u"js::jit::JitCode";
    362 const char16_t Concrete<js::Shape>::concreteTypeName[] = u"js::Shape";
    363 const char16_t Concrete<js::BaseShape>::concreteTypeName[] = u"js::BaseShape";
    364 const char16_t Concrete<js::GetterSetter>::concreteTypeName[] =
    365    u"js::GetterSetter";
    366 const char16_t Concrete<js::PropMap>::concreteTypeName[] = u"js::PropMap";
    367 const char16_t Concrete<js::Scope>::concreteTypeName[] = u"js::Scope";
    368 const char16_t Concrete<js::RegExpShared>::concreteTypeName[] =
    369    u"js::RegExpShared";
    370 
    371 namespace JS {
    372 namespace ubi {
    373 
    374 RootList::RootList(JSContext* cx, bool wantNames /* = false */)
    375    : cx(cx), wantNames(wantNames), inited(false) {}
    376 
    377 std::pair<bool, JS::AutoCheckCannotGC> RootList::init() {
    378  EdgeVectorTracer tracer(cx->runtime(), &edges, wantNames);
    379  js::TraceRuntime(&tracer);
    380  inited = tracer.okay;
    381  return {tracer.okay, JS::AutoCheckCannotGC(cx)};
    382 }
    383 
    384 std::pair<bool, JS::AutoCheckCannotGC> RootList::init(
    385    CompartmentSet& debuggees) {
    386  EdgeVector allRootEdges;
    387  EdgeVectorTracer tracer(cx->runtime(), &allRootEdges, wantNames);
    388 
    389  ZoneSet debuggeeZones;
    390  for (auto range = debuggees.all(); !range.empty(); range.popFront()) {
    391    if (!debuggeeZones.put(range.front()->zone())) {
    392      return {false, JS::AutoCheckCannotGC(cx)};
    393    }
    394  }
    395 
    396  js::TraceRuntime(&tracer);
    397  if (!tracer.okay) {
    398    return {false, JS::AutoCheckCannotGC(cx)};
    399  }
    400  js::gc::TraceIncomingCCWs(&tracer, debuggees);
    401  if (!tracer.okay) {
    402    return {false, JS::AutoCheckCannotGC(cx)};
    403  }
    404 
    405  for (EdgeVector::Range r = allRootEdges.all(); !r.empty(); r.popFront()) {
    406    Edge& edge = r.front();
    407 
    408    JS::Compartment* compartment = edge.referent.compartment();
    409    if (compartment && !debuggees.has(compartment)) {
    410      continue;
    411    }
    412 
    413    Zone* zone = edge.referent.zone();
    414    if (zone && !debuggeeZones.has(zone)) {
    415      continue;
    416    }
    417 
    418    if (!edges.append(std::move(edge))) {
    419      return {false, JS::AutoCheckCannotGC(cx)};
    420    }
    421  }
    422 
    423  inited = true;
    424  return {true, JS::AutoCheckCannotGC(cx)};
    425 }
    426 
    427 std::pair<bool, JS::AutoCheckCannotGC> RootList::init(HandleObject debuggees) {
    428  MOZ_ASSERT(debuggees && JS::dbg::IsDebugger(*debuggees));
    429  js::Debugger* dbg = js::Debugger::fromJSObject(debuggees.get());
    430 
    431  CompartmentSet debuggeeCompartments;
    432 
    433  for (js::WeakGlobalObjectSet::Range r = dbg->allDebuggees(); !r.empty();
    434       r.popFront()) {
    435    if (!debuggeeCompartments.put(r.front()->compartment())) {
    436      return {false, JS::AutoCheckCannotGC(cx)};
    437    }
    438  }
    439 
    440  auto [ok, nogc] = init(debuggeeCompartments);
    441  if (!ok) {
    442    return {false, nogc};
    443  }
    444 
    445  // Ensure that each of our debuggee globals are in the root list.
    446  for (js::WeakGlobalObjectSet::Range r = dbg->allDebuggees(); !r.empty();
    447       r.popFront()) {
    448    if (!addRoot(JS::ubi::Node(static_cast<JSObject*>(r.front())),
    449                 u"debuggee global")) {
    450      return {false, nogc};
    451    }
    452  }
    453 
    454  inited = true;
    455  return {true, nogc};
    456 }
    457 
    458 bool RootList::addRoot(Node node, const char16_t* edgeName) {
    459  MOZ_ASSERT_IF(wantNames, edgeName);
    460 
    461  UniqueTwoByteChars name;
    462  if (edgeName) {
    463    name = js::DuplicateString(edgeName);
    464    if (!name) {
    465      return false;
    466    }
    467  }
    468 
    469  return edges.append(Edge(name.release(), node));
    470 }
    471 
    472 const char16_t Concrete<RootList>::concreteTypeName[] = u"JS::ubi::RootList";
    473 
    474 UniquePtr<EdgeRange> Concrete<RootList>::edges(JSContext* cx,
    475                                               bool wantNames) const {
    476  MOZ_ASSERT_IF(wantNames, get().wantNames);
    477  return js::MakeUnique<PreComputedEdgeRange>(get().edges);
    478 }
    479 
    480 bool SimpleEdgeRange::addTracerEdges(JSRuntime* rt, void* thing,
    481                                     JS::TraceKind kind, bool wantNames) {
    482  EdgeVectorTracer tracer(rt, &edges, wantNames);
    483  JS::TraceChildren(&tracer, JS::GCCellPtr(thing, kind));
    484  settle();
    485  return tracer.okay;
    486 }
    487 
    488 void Concrete<JSObject>::construct(void* storage, JSObject* ptr) {
    489  if (ptr) {
    490    auto clasp = ptr->getClass();
    491    auto callback = ptr->compartment()
    492                        ->runtimeFromMainThread()
    493                        ->constructUbiNodeForDOMObjectCallback;
    494    if (clasp->isDOMClass() && callback) {
    495      AutoSuppressGCAnalysis suppress;
    496      callback(storage, ptr);
    497      return;
    498    }
    499  }
    500  new (storage) Concrete(ptr);
    501 }
    502 
    503 void SetConstructUbiNodeForDOMObjectCallback(JSContext* cx,
    504                                             void (*callback)(void*,
    505                                                              JSObject*)) {
    506  cx->runtime()->constructUbiNodeForDOMObjectCallback = callback;
    507 }
    508 
    509 JS_PUBLIC_API const char* CoarseTypeToString(CoarseType type) {
    510  switch (type) {
    511    case CoarseType::Other:
    512      return "Other";
    513    case CoarseType::Object:
    514      return "Object";
    515    case CoarseType::Script:
    516      return "Script";
    517    case CoarseType::String:
    518      return "String";
    519    case CoarseType::DOMNode:
    520      return "DOMNode";
    521    default:
    522      return "Unknown";
    523  }
    524 };
    525 
    526 }  // namespace ubi
    527 }  // namespace JS