Quellcodebibliothek Statistik Leitseite products/sources/formale Sprachen/C/Firefox/js/src/vm/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 40 kB image not shown  

Quelle  UbiNodeCensus.cpp   Sprache: C

 
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 * vim: set ts=8 sts=2 et sw=2 tw=80:
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */


#include "js/UbiNodeCensus.h"

#include "mozilla/ScopeExit.h"

#include "builtin/MapObject.h"
#include "js/friend/ErrorMessages.h"  // js::GetErrorMessage, JSMSG_*
#include "js/Printer.h"
#include "util/Text.h"
#include "vm/Compartment.h"
#include "vm/JSContext.h"
#include "vm/PlainObject.h"  // js::PlainObject

#include "vm/NativeObject-inl.h"

using namespace js;

namespace JS {
namespace ubi {

JS_PUBLIC_API void CountDeleter::operator()(CountBase* ptr) {
  if (!ptr) {
    return;
  }

  // Downcast to our true type and destruct, as guided by our CountType
  // pointer.
  ptr->destruct();
  js_free(ptr);
}

/*** Count Types ************************************************************/

// The simplest type: just count everything.
class SimpleCount : public CountType {
  struct Count : CountBase {
    size_t totalBytes_;

    explicit Count(SimpleCount& count) : CountBase(count), totalBytes_(0) {}
  };

  UniqueTwoByteChars label;
  bool reportCount : 1;
  bool reportBytes : 1;

 public:
  explicit SimpleCount(UniqueTwoByteChars& label, bool reportCount = true,
                       bool reportBytes = true)
      : label(std::move(label)),
        reportCount(reportCount),
        reportBytes(reportBytes) {}

  explicit SimpleCount()
      : label(nullptr), reportCount(true), reportBytes(true) {}

  void destructCount(CountBase& countBase) override {
    Count& count = static_cast<Count&>(countBase);
    count.~Count();
  }

  CountBasePtr makeCount() override {
    return CountBasePtr(js_new<Count>(*this));
  }
  void traceCount(CountBase& countBase, JSTracer* trc) override {}
  bool count(CountBase& countBase, mozilla::MallocSizeOf mallocSizeOf,
             const Node& node) override;
  bool report(JSContext* cx, CountBase& countBase,
              MutableHandleValue report) override;
};

bool SimpleCount::count(CountBase& countBase,
                        mozilla::MallocSizeOf mallocSizeOf, const Node& node) {
  Count& count = static_cast<Count&>(countBase);
  if (reportBytes) {
    count.totalBytes_ += node.size(mallocSizeOf);
  }
  return true;
}

bool SimpleCount::report(JSContext* cx, CountBase& countBase,
                         MutableHandleValue report) {
  Count& count = static_cast<Count&>(countBase);

  Rooted<PlainObject*> obj(cx, NewPlainObject(cx));
  if (!obj) {
    return false;
  }

  RootedValue countValue(cx, NumberValue(count.total_));
  if (reportCount &&
      !DefineDataProperty(cx, obj, cx->names().count, countValue)) {
    return false;
  }

  RootedValue bytesValue(cx, NumberValue(count.totalBytes_));
  if (reportBytes &&
      !DefineDataProperty(cx, obj, cx->names().bytes, bytesValue)) {
    return false;
  }

  if (label) {
    JSString* labelString = JS_NewUCStringCopyZ(cx, label.get());
    if (!labelString) {
      return false;
    }
    RootedValue labelValue(cx, StringValue(labelString));
    if (!DefineDataProperty(cx, obj, cx->names().label, labelValue)) {
      return false;
    }
  }

  report.setObject(*obj);
  return true;
}

// A count type that collects all matching nodes in a bucket.
class BucketCount : public CountType {
  struct Count : CountBase {
    JS::ubi::Vector<JS::ubi::Node::Id> ids_;

    explicit Count(BucketCount& count) : CountBase(count) {}
  };

 public:
  explicit BucketCount() = default;

  void destructCount(CountBase& countBase) override {
    Count& count = static_cast<Count&>(countBase);
    count.~Count();
  }

  CountBasePtr makeCount() override {
    return CountBasePtr(js_new<Count>(*this));
  }
  void traceCount(CountBase& countBase, JSTracer* trc) final {}
  bool count(CountBase& countBase, mozilla::MallocSizeOf mallocSizeOf,
             const Node& node) override;
  bool report(JSContext* cx, CountBase& countBase,
              MutableHandleValue report) override;
};

bool BucketCount::count(CountBase& countBase,
                        mozilla::MallocSizeOf mallocSizeOf, const Node& node) {
  Count& count = static_cast<Count&>(countBase);
  return count.ids_.append(node.identifier());
}

bool BucketCount::report(JSContext* cx, CountBase& countBase,
                         MutableHandleValue report) {
  Count& count = static_cast<Count&>(countBase);

  size_t length = count.ids_.length();
  Rooted<ArrayObject*> arr(cx, NewDenseFullyAllocatedArray(cx, length));
  if (!arr) {
    return false;
  }
  arr->ensureDenseInitializedLength(0, length);

  for (size_t i = 0; i < length; i++) {
    arr->setDenseElement(i, NumberValue(count.ids_[i]));
  }

  report.setObject(*arr);
  return true;
}

// A type that categorizes nodes by their JavaScript type -- 'objects',
// 'strings', 'scripts', 'domNode', and 'other' -- and then passes the nodes to
// child types.
//
// Implementation details of scripts like jitted code are counted under
// 'scripts'.
class ByCoarseType : public CountType {
  CountTypePtr objects;
  CountTypePtr scripts;
  CountTypePtr strings;
  CountTypePtr other;
  CountTypePtr domNode;

  struct Count : CountBase {
    Count(CountType& type, CountBasePtr& objects, CountBasePtr& scripts,
          CountBasePtr& strings, CountBasePtr& other, CountBasePtr& domNode)
        : CountBase(type),
          objects(std::move(objects)),
          scripts(std::move(scripts)),
          strings(std::move(strings)),
          other(std::move(other)),
          domNode(std::move(domNode)) {}

    CountBasePtr objects;
    CountBasePtr scripts;
    CountBasePtr strings;
    CountBasePtr other;
    CountBasePtr domNode;
  };

 public:
  ByCoarseType(CountTypePtr& objects, CountTypePtr& scripts,
               CountTypePtr& strings, CountTypePtr& other,
               CountTypePtr& domNode)
      : objects(std::move(objects)),
        scripts(std::move(scripts)),
        strings(std::move(strings)),
        other(std::move(other)),
        domNode(std::move(domNode)) {}

  void destructCount(CountBase& countBase) override {
    Count& count = static_cast<Count&>(countBase);
    count.~Count();
  }

  CountBasePtr makeCount() override;
  void traceCount(CountBase& countBase, JSTracer* trc) override;
  bool count(CountBase& countBase, mozilla::MallocSizeOf mallocSizeOf,
             const Node& node) override;
  bool report(JSContext* cx, CountBase& countBase,
              MutableHandleValue report) override;
};

CountBasePtr ByCoarseType::makeCount() {
  CountBasePtr objectsCount(objects->makeCount());
  CountBasePtr scriptsCount(scripts->makeCount());
  CountBasePtr stringsCount(strings->makeCount());
  CountBasePtr otherCount(other->makeCount());
  CountBasePtr domNodeCount(domNode->makeCount());

  if (!objectsCount || !scriptsCount || !stringsCount || !otherCount ||
      !domNodeCount) {
    return CountBasePtr(nullptr);
  }

  return CountBasePtr(js_new<Count>(*this, objectsCount, scriptsCount,
                                    stringsCount, otherCount, domNodeCount));
}

void ByCoarseType::traceCount(CountBase& countBase, JSTracer* trc) {
  Count& count = static_cast<Count&>(countBase);
  count.objects->trace(trc);
  count.scripts->trace(trc);
  count.strings->trace(trc);
  count.other->trace(trc);
  count.domNode->trace(trc);
}

bool ByCoarseType::count(CountBase& countBase,
                         mozilla::MallocSizeOf mallocSizeOf, const Node& node) {
  Count& count = static_cast<Count&>(countBase);

  switch (node.coarseType()) {
    case JS::ubi::CoarseType::Object:
      return count.objects->count(mallocSizeOf, node);
    case JS::ubi::CoarseType::Script:
      return count.scripts->count(mallocSizeOf, node);
    case JS::ubi::CoarseType::String:
      return count.strings->count(mallocSizeOf, node);
    case JS::ubi::CoarseType::Other:
      return count.other->count(mallocSizeOf, node);
    case JS::ubi::CoarseType::DOMNode:
      return count.domNode->count(mallocSizeOf, node);
    default:
      MOZ_CRASH("bad JS::ubi::CoarseType in JS::ubi::ByCoarseType::count");
      return false;
  }
}

bool ByCoarseType::report(JSContext* cx, CountBase& countBase,
                          MutableHandleValue report) {
  Count& count = static_cast<Count&>(countBase);

  Rooted<PlainObject*> obj(cx, NewPlainObject(cx));
  if (!obj) {
    return false;
  }

  RootedValue objectsReport(cx);
  if (!count.objects->report(cx, &objectsReport) ||
      !DefineDataProperty(cx, obj, cx->names().objects, objectsReport))
    return false;

  RootedValue scriptsReport(cx);
  if (!count.scripts->report(cx, &scriptsReport) ||
      !DefineDataProperty(cx, obj, cx->names().scripts, scriptsReport))
    return false;

  RootedValue stringsReport(cx);
  if (!count.strings->report(cx, &stringsReport) ||
      !DefineDataProperty(cx, obj, cx->names().strings, stringsReport))
    return false;

  RootedValue otherReport(cx);
  if (!count.other->report(cx, &otherReport) ||
      !DefineDataProperty(cx, obj, cx->names().other, otherReport))
    return false;
  RootedValue domReport(cx);
  if (!count.domNode->report(cx, &domReport) ||
      !DefineDataProperty(cx, obj, cx->names().domNode, domReport))
    return false;

  report.setObject(*obj);
  return true;
}

// Comparison function for sorting hash table entries by the smallest node ID
// they counted. Node IDs are stable and unique, which ensures ordering of
// results never depends on hash table placement or sort algorithm vagaries. The
// arguments are doubly indirect: they're pointers to elements in an array of
// pointers to table entries.
template <typename Entry>
static int compareEntries(const void* lhsVoid, const void* rhsVoid) {
  auto lhs = (*static_cast<const Entry* const*>(lhsVoid))
                 ->value()
                 ->smallestNodeIdCounted_;
  auto rhs = (*static_cast<const Entry* const*>(rhsVoid))
                 ->value()
                 ->smallestNodeIdCounted_;

  // We don't want to just subtract the values, as they're unsigned.
  if (lhs < rhs) {
    return 1;
  }
  if (lhs > rhs) {
    return -1;
  }
  return 0;
}

// A hash map mapping from C strings to counts.
using CStringCountMap = HashMap<const char*, CountBasePtr,
                                mozilla::CStringHasher, SystemAllocPolicy>;

// Convert a HashMap into an object with each key one of the entries from the
// map and each value the associated count's report. For use during census
// reporting.
//
// `Map` must be a `HashMap` from some key type to a `CountBasePtr`.
//
// `GetName` must be a callable type which takes `const Map::Key&` and returns
// `JSAtom*`.
template <class Map, class GetName>
static PlainObject* countMapToObject(JSContext* cx, Map& map, GetName getName) {
  // Build a vector of pointers to entries; sort by total; and then use
  // that to build the result object. This makes the ordering of entries
  // more interesting, and a little less non-deterministic.

  JS::ubi::Vector<typename Map::Entry*> entries;
  if (!entries.reserve(map.count())) {
    ReportOutOfMemory(cx);
    return nullptr;
  }

  for (auto r = map.all(); !r.empty(); r.popFront()) {
    entries.infallibleAppend(&r.front());
  }

  if (entries.length()) {
    qsort(entries.begin(), entries.length(), sizeof(*entries.begin()),
          compareEntries<typename Map::Entry>);
  }

  Rooted<PlainObject*> obj(cx, NewPlainObject(cx));
  if (!obj) {
    return nullptr;
  }

  for (auto& entry : entries) {
    CountBasePtr& thenCount = entry->value();
    RootedValue thenReport(cx);
    if (!thenCount->report(cx, &thenReport)) {
      return nullptr;
    }

    JSAtom* atom = getName(entry->key());
    if (!atom) {
      return nullptr;
    }

    RootedId entryId(cx, AtomToId(atom));
    if (!DefineDataProperty(cx, obj, entryId, thenReport)) {
      return nullptr;
    }
  }

  return obj;
}

// A type that categorizes nodes that are JSObjects by their class name,
// and places all other nodes in an 'other' category.
class ByObjectClass : public CountType {
  // A table mapping class names to their counts. Note that we treat js::Class
  // instances with the same name as equal keys. If you have several
  // js::Classes with equal names (and we do; as of this writing there were
  // six named "Object"), you will get several different js::Classes being
  // counted in the same table entry.
  using Table = CStringCountMap;
  using Entry = Table::Entry;

  struct Count : public CountBase {
    Table table;
    CountBasePtr other;

    Count(CountType& type, CountBasePtr& other)
        : CountBase(type), other(std::move(other)) {}
  };

  CountTypePtr classesType;
  CountTypePtr otherType;

 public:
  ByObjectClass(CountTypePtr& classesType, CountTypePtr& otherType)
      : classesType(std::move(classesType)), otherType(std::move(otherType)) {}

  void destructCount(CountBase& countBase) override {
    Count& count = static_cast<Count&>(countBase);
    count.~Count();
  }

  CountBasePtr makeCount() override;
  void traceCount(CountBase& countBase, JSTracer* trc) override;
  bool count(CountBase& countBase, mozilla::MallocSizeOf mallocSizeOf,
             const Node& node) override;
  bool report(JSContext* cx, CountBase& countBase,
              MutableHandleValue report) override;
};

CountBasePtr ByObjectClass::makeCount() {
  CountBasePtr otherCount(otherType->makeCount());
  if (!otherCount) {
    return nullptr;
  }

  auto count = js::MakeUnique<Count>(*this, otherCount);
  if (!count) {
    return nullptr;
  }

  return CountBasePtr(count.release());
}

void ByObjectClass::traceCount(CountBase& countBase, JSTracer* trc) {
  Count& count = static_cast<Count&>(countBase);
  for (Table::Range r = count.table.all(); !r.empty(); r.popFront()) {
    r.front().value()->trace(trc);
  }
  count.other->trace(trc);
}

bool ByObjectClass::count(CountBase& countBase,
                          mozilla::MallocSizeOf mallocSizeOf,
                          const Node& node) {
  Count& count = static_cast<Count&>(countBase);

  const char* className = node.jsObjectClassName();
  if (!className) {
    return count.other->count(mallocSizeOf, node);
  }

  Table::AddPtr p = count.table.lookupForAdd(className);
  if (!p) {
    CountBasePtr classCount(classesType->makeCount());
    if (!classCount || !count.table.add(p, className, std::move(classCount))) {
      return false;
    }
  }
  return p->value()->count(mallocSizeOf, node);
}

bool ByObjectClass::report(JSContext* cx, CountBase& countBase,
                           MutableHandleValue report) {
  Count& count = static_cast<Count&>(countBase);

  Rooted<PlainObject*> obj(
      cx, countMapToObject(cx, count.table, [cx](const char* key) {
        MOZ_ASSERT(key);
        return Atomize(cx, key, strlen(key));
      }));
  if (!obj) {
    return false;
  }

  RootedValue otherReport(cx);
  if (!count.other->report(cx, &otherReport) ||
      !DefineDataProperty(cx, obj, cx->names().other, otherReport))
    return false;

  report.setObject(*obj);
  return true;
}

class ByDomObjectClass : public CountType {
  // A table mapping descriptive names to their counts.
  using UniqueC16String = JS::UniqueTwoByteChars;

  struct UniqueC16StringHasher {
    using Lookup = UniqueC16String;

    static js::HashNumber hash(const Lookup& lookup) {
      return mozilla::HashString(lookup.get());
    }

    static bool match(const UniqueC16String& key, const Lookup& lookup) {
      return CompareChars(key.get(), js_strlen(key.get()), lookup.get(),
                          js_strlen(lookup.get())) == 0;
    }
  };

  using Table = HashMap<UniqueC16String, CountBasePtr, UniqueC16StringHasher,
                        SystemAllocPolicy>;
  using Entry = Table::Entry;

  struct Count : public CountBase {
    Table table;

    explicit Count(CountType& type) : CountBase(type) {}
  };

  CountTypePtr classesType;

 public:
  explicit ByDomObjectClass(CountTypePtr& classesType)
      : classesType(std::move(classesType)) {}

  void destructCount(CountBase& countBase) override {
    Count& count = static_cast<Count&>(countBase);
    count.~Count();
  }

  CountBasePtr makeCount() override;
  void traceCount(CountBase& countBase, JSTracer* trc) override;
  bool count(CountBase& countBase, mozilla::MallocSizeOf mallocSizeOf,
             const Node& node) override;
  bool report(JSContext* cx, CountBase& countBase,
              MutableHandleValue report) override;
};

CountBasePtr ByDomObjectClass::makeCount() {
  auto count = js::MakeUnique<Count>(*this);
  if (!count) {
    return nullptr;
  }

  return CountBasePtr(count.release());
}

void ByDomObjectClass::traceCount(CountBase& countBase, JSTracer* trc) {
  Count& count = static_cast<Count&>(countBase);
  for (Table::Range r = count.table.all(); !r.empty(); r.popFront()) {
    r.front().value()->trace(trc);
  }
}

bool ByDomObjectClass::count(CountBase& countBase,
                             mozilla::MallocSizeOf mallocSizeOf,
                             const Node& node) {
  Count& count = static_cast<Count&>(countBase);

  const char16_t* nodeName = node.descriptiveTypeName();
  if (!nodeName) {
    return false;
  }

  UniqueC16String name = DuplicateString(nodeName);
  if (!name) {
    return false;
  }

  Table::AddPtr p = count.table.lookupForAdd(name);
  if (!p) {
    CountBasePtr classesCount(classesType->makeCount());
    if (!classesCount ||
        !count.table.add(p, std::move(name), std::move(classesCount))) {
      return false;
    }
  }
  return p->value()->count(mallocSizeOf, node);
}

bool ByDomObjectClass::report(JSContext* cx, CountBase& countBase,
                              MutableHandleValue report) {
  Count& count = static_cast<Count&>(countBase);

  Rooted<PlainObject*> obj(
      cx, countMapToObject(cx, count.table, [cx](const UniqueC16String& key) {
        const char16_t* chars = key.get();
        MOZ_ASSERT(chars);
        return AtomizeChars(cx, chars, js_strlen(chars));
      }));
  if (!obj) {
    return false;
  }

  report.setObject(*obj);
  return true;
}

// A count type that categorizes nodes by their ubi::Node::typeName.
class ByUbinodeType : public CountType {
  // Note that, because ubi::Node::typeName promises to return a specific
  // pointer, not just any string whose contents are correct, we can use their
  // addresses as hash table keys.
  using Table = HashMap<const char16_t*, CountBasePtr,
                        DefaultHasher<const char16_t*>, SystemAllocPolicy>;
  using Entry = Table::Entry;

  struct Count : public CountBase {
    Table table;

    explicit Count(CountType& type) : CountBase(type) {}
  };

  CountTypePtr entryType;

 public:
  explicit ByUbinodeType(CountTypePtr& entryType)
      : entryType(std::move(entryType)) {}

  void destructCount(CountBase& countBase) override {
    Count& count = static_cast<Count&>(countBase);
    count.~Count();
  }

  CountBasePtr makeCount() override;
  void traceCount(CountBase& countBase, JSTracer* trc) override;
  bool count(CountBase& countBase, mozilla::MallocSizeOf mallocSizeOf,
             const Node& node) override;
  bool report(JSContext* cx, CountBase& countBase,
              MutableHandleValue report) override;
};

CountBasePtr ByUbinodeType::makeCount() {
  auto count = js::MakeUnique<Count>(*this);
  if (!count) {
    return nullptr;
  }

  return CountBasePtr(count.release());
}

void ByUbinodeType::traceCount(CountBase& countBase, JSTracer* trc) {
  Count& count = static_cast<Count&>(countBase);
  for (Table::Range r = count.table.all(); !r.empty(); r.popFront()) {
    r.front().value()->trace(trc);
  }
}

bool ByUbinodeType::count(CountBase& countBase,
                          mozilla::MallocSizeOf mallocSizeOf,
                          const Node& node) {
  Count& count = static_cast<Count&>(countBase);

  const char16_t* key = node.typeName();
  MOZ_ASSERT(key);
  Table::AddPtr p = count.table.lookupForAdd(key);
  if (!p) {
    CountBasePtr typesCount(entryType->makeCount());
    if (!typesCount || !count.table.add(p, key, std::move(typesCount))) {
      return false;
    }
  }
  return p->value()->count(mallocSizeOf, node);
}

bool ByUbinodeType::report(JSContext* cx, CountBase& countBase,
                           MutableHandleValue report) {
  Count& count = static_cast<Count&>(countBase);

  // Build a vector of pointers to entries; sort by total; and then use
  // that to build the result object. This makes the ordering of entries
  // more interesting, and a little less non-deterministic.
  JS::ubi::Vector<Entry*> entries;
  if (!entries.reserve(count.table.count())) {
    return false;
  }
  for (Table::Range r = count.table.all(); !r.empty(); r.popFront()) {
    entries.infallibleAppend(&r.front());
  }
  if (entries.length()) {
    qsort(entries.begin(), entries.length(), sizeof(*entries.begin()),
          compareEntries<Entry>);
  }

  // Now build the result by iterating over the sorted vector.
  Rooted<PlainObject*> obj(cx, NewPlainObject(cx));
  if (!obj) {
    return false;
  }
  for (Entry** entryPtr = entries.begin(); entryPtr < entries.end();
       entryPtr++) {
    Entry& entry = **entryPtr;
    CountBasePtr& typeCount = entry.value();
    RootedValue typeReport(cx);
    if (!typeCount->report(cx, &typeReport)) {
      return false;
    }

    const char16_t* name = entry.key();
    MOZ_ASSERT(name);
    JSAtom* atom = AtomizeChars(cx, name, js_strlen(name));
    if (!atom) {
      return false;
    }
    RootedId entryId(cx, AtomToId(atom));

    if (!DefineDataProperty(cx, obj, entryId, typeReport)) {
      return false;
    }
  }

  report.setObject(*obj);
  return true;
}

// A count type that categorizes nodes by the JS stack under which they were
// allocated.
class ByAllocationStack : public CountType {
  using Table = HashMap<StackFrame, CountBasePtr, DefaultHasher<StackFrame>,
                        SystemAllocPolicy>;
  using Entry = Table::Entry;

  struct Count : public CountBase {
    // NOTE: You may look up entries in this table by JS::ubi::StackFrame
    // key only during traversal, NOT ONCE TRAVERSAL IS COMPLETE. Once
    // traversal is complete, you may only iterate over it.
    //
    // In this hash table, keys are JSObjects (with some indirection), and
    // we use JSObject identity (that is, address identity) as key
    // identity. The normal way to support such a table is to make the trace
    // function notice keys that have moved and re-key them in the
    // table. However, our trace function does *not* rehash; the first GC
    // may render the hash table unsearchable.
    //
    // This is as it should be:
    //
    // First, the heap traversal phase needs lookups by key to work. But no
    // GC may ever occur during a traversal; this is enforced by the
    // JS::ubi::BreadthFirst template. So the traceCount function doesn't
    // need to do anything to help traversal; it never even runs then.
    //
    // Second, the report phase needs iteration over the table to work, but
    // never looks up entries by key. GC may well occur during this phase:
    // we allocate a Map object, and probably cross-compartment wrappers for
    // SavedFrame instances as well. If a GC were to occur, it would call
    // our traceCount function; if traceCount were to re-key, that would
    // ruin the traversal in progress.
    //
    // So depending on the phase, we either don't need re-keying, or
    // can't abide it.
    Table table;
    CountBasePtr noStack;

    Count(CountType& type, CountBasePtr& noStack)
        : CountBase(type), noStack(std::move(noStack)) {}
  };

  CountTypePtr entryType;
  CountTypePtr noStackType;

 public:
  ByAllocationStack(CountTypePtr& entryType, CountTypePtr& noStackType)
      : entryType(std::move(entryType)), noStackType(std::move(noStackType)) {}

  void destructCount(CountBase& countBase) override {
    Count& count = static_cast<Count&>(countBase);
    count.~Count();
  }

  CountBasePtr makeCount() override;
  void traceCount(CountBase& countBase, JSTracer* trc) override;
  bool count(CountBase& countBase, mozilla::MallocSizeOf mallocSizeOf,
             const Node& node) override;
  bool report(JSContext* cx, CountBase& countBase,
              MutableHandleValue report) override;
};

CountBasePtr ByAllocationStack::makeCount() {
  CountBasePtr noStackCount(noStackType->makeCount());
  if (!noStackCount) {
    return nullptr;
  }

  auto count = js::MakeUnique<Count>(*this, noStackCount);
  if (!count) {
    return nullptr;
  }
  return CountBasePtr(count.release());
}

void ByAllocationStack::traceCount(CountBase& countBase, JSTracer* trc) {
  Count& count = static_cast<Count&>(countBase);
  for (Table::Range r = count.table.all(); !r.empty(); r.popFront()) {
    // Trace our child Counts.
    r.front().value()->trace(trc);

    // Trace the StackFrame that is this entry's key. Do not re-key if
    // it has moved; see comments for ByAllocationStack::Count::table.
    const StackFrame* key = &r.front().key();
    auto& k = *const_cast<StackFrame*>(key);
    k.trace(trc);
  }
  count.noStack->trace(trc);
}

bool ByAllocationStack::count(CountBase& countBase,
                              mozilla::MallocSizeOf mallocSizeOf,
                              const Node& node) {
  Count& count = static_cast<Count&>(countBase);

  // If we do have an allocation stack for this node, include it in the
  // count for that stack.
  if (node.hasAllocationStack()) {
    auto allocationStack = node.allocationStack();
    auto p = count.table.lookupForAdd(allocationStack);
    if (!p) {
      CountBasePtr stackCount(entryType->makeCount());
      if (!stackCount ||
          !count.table.add(p, allocationStack, std::move(stackCount))) {
        return false;
      }
    }
    MOZ_ASSERT(p);
    return p->value()->count(mallocSizeOf, node);
  }

  // Otherwise, count it in the "no stack" category.
  return count.noStack->count(mallocSizeOf, node);
}

bool ByAllocationStack::report(JSContext* cx, CountBase& countBase,
                               MutableHandleValue report) {
  Count& count = static_cast<Count&>(countBase);

#ifdef DEBUG
  // Check that nothing rehashes our table while we hold pointers into it.
  mozilla::Generation generation = count.table.generation();
#endif

  // Build a vector of pointers to entries; sort by total; and then use
  // that to build the result object. This makes the ordering of entries
  // more interesting, and a little less non-deterministic.
  JS::ubi::Vector<Entry*> entries;
  if (!entries.reserve(count.table.count())) {
    return false;
  }
  for (Table::Range r = count.table.all(); !r.empty(); r.popFront()) {
    entries.infallibleAppend(&r.front());
  }
  if (entries.length()) {
    qsort(entries.begin(), entries.length(), sizeof(*entries.begin()),
          compareEntries<Entry>);
  }

  // Now build the result by iterating over the sorted vector.
  Rooted<MapObject*> map(cx, MapObject::create(cx));
  if (!map) {
    return false;
  }
  for (Entry** entryPtr = entries.begin(); entryPtr < entries.end();
       entryPtr++) {
    Entry& entry = **entryPtr;
    MOZ_ASSERT(entry.key());

    RootedObject stack(cx);
    if (!entry.key().constructSavedFrameStack(cx, &stack) ||
        !cx->compartment()->wrap(cx, &stack)) {
      return false;
    }
    RootedValue stackVal(cx, ObjectValue(*stack));

    CountBasePtr& stackCount = entry.value();
    RootedValue stackReport(cx);
    if (!stackCount->report(cx, &stackReport)) {
      return false;
    }

    if (!map->set(cx, stackVal, stackReport)) {
      return false;
    }
  }

  if (count.noStack->total_ > 0) {
    RootedValue noStackReport(cx);
    if (!count.noStack->report(cx, &noStackReport)) {
      return false;
    }
    RootedValue noStack(cx, StringValue(cx->names().noStack));
    if (!map->set(cx, noStack, noStackReport)) {
      return false;
    }
  }

  MOZ_ASSERT(generation == count.table.generation());

  report.setObject(*map);
  return true;
}

// A count type that categorizes nodes by their script's filename.
class ByFilename : public CountType {
  using UniqueCString = JS::UniqueChars;

  struct UniqueCStringHasher {
    using Lookup = UniqueCString;

    static js::HashNumber hash(const Lookup& lookup) {
      return mozilla::CStringHasher::hash(lookup.get());
    }

    static bool match(const UniqueCString& key, const Lookup& lookup) {
      return mozilla::CStringHasher::match(key.get(), lookup.get());
    }
  };

  // A table mapping filenames to their counts. Note that we treat scripts
  // with the same filename as equivalent. If you have several sources with
  // the same filename, then all their scripts will get bucketed together.
  using Table = HashMap<UniqueCString, CountBasePtr, UniqueCStringHasher,
                        SystemAllocPolicy>;
  using Entry = Table::Entry;

  struct Count : public CountBase {
    Table table;
    CountBasePtr then;
    CountBasePtr noFilename;

    Count(CountType& type, CountBasePtr&& then, CountBasePtr&& noFilename)
        : CountBase(type),
          then(std::move(then)),
          noFilename(std::move(noFilename)) {}
  };

  CountTypePtr thenType;
  CountTypePtr noFilenameType;

 public:
  ByFilename(CountTypePtr&& thenType, CountTypePtr&& noFilenameType)
      : thenType(std::move(thenType)),
        noFilenameType(std::move(noFilenameType)) {}

  void destructCount(CountBase& countBase) override {
    Count& count = static_cast<Count&>(countBase);
    count.~Count();
  }

  CountBasePtr makeCount() override;
  void traceCount(CountBase& countBase, JSTracer* trc) override;
  bool count(CountBase& countBase, mozilla::MallocSizeOf mallocSizeOf,
             const Node& node) override;
  bool report(JSContext* cx, CountBase& countBase,
              MutableHandleValue report) override;
};

CountBasePtr ByFilename::makeCount() {
  CountBasePtr thenCount(thenType->makeCount());
  if (!thenCount) {
    return nullptr;
  }

  CountBasePtr noFilenameCount(noFilenameType->makeCount());
  if (!noFilenameCount) {
    return nullptr;
  }

  auto count = js::MakeUnique<Count>(*this, std::move(thenCount),
                                     std::move(noFilenameCount));
  if (!count) {
    return nullptr;
  }

  return CountBasePtr(count.release());
}

void ByFilename::traceCount(CountBase& countBase, JSTracer* trc) {
  Count& count = static_cast<Count&>(countBase);
  for (Table::Range r = count.table.all(); !r.empty(); r.popFront()) {
    r.front().value()->trace(trc);
  }
  count.noFilename->trace(trc);
}

bool ByFilename::count(CountBase& countBase, mozilla::MallocSizeOf mallocSizeOf,
                       const Node& node) {
  Count& count = static_cast<Count&>(countBase);

  const char* filename = node.scriptFilename();
  if (!filename) {
    return count.noFilename->count(mallocSizeOf, node);
  }

  UniqueCString myFilename = DuplicateString(filename);
  if (!myFilename) {
    return false;
  }

  Table::AddPtr p = count.table.lookupForAdd(myFilename);
  if (!p) {
    CountBasePtr thenCount(thenType->makeCount());
    if (!thenCount ||
        !count.table.add(p, std::move(myFilename), std::move(thenCount))) {
      return false;
    }
  }
  return p->value()->count(mallocSizeOf, node);
}

bool ByFilename::report(JSContext* cx, CountBase& countBase,
                        MutableHandleValue report) {
  Count& count = static_cast<Count&>(countBase);

  Rooted<PlainObject*> obj(
      cx, countMapToObject(cx, count.table, [cx](const UniqueCString& key) {
        const char* utf8chars = key.get();
        return AtomizeUTF8Chars(cx, utf8chars, strlen(utf8chars));
      }));
  if (!obj) {
    return false;
  }

  RootedValue noFilenameReport(cx);
  if (!count.noFilename->report(cx, &noFilenameReport) ||
      !DefineDataProperty(cx, obj, cx->names().noFilename, noFilenameReport)) {
    return false;
  }

  report.setObject(*obj);
  return true;
}

/*** Census Handler *********************************************************/

JS_PUBLIC_API bool CensusHandler::operator()(
    BreadthFirst<CensusHandler>& traversal, Node origin, const Edge& edge,
    NodeData* referentData, bool first) {
  // We're only interested in the first time we reach edge.referent, not
  // in every edge arriving at that node.
  if (!first) {
    return true;
  }

  // Don't count nodes outside the debuggee zones. Do count things in the
  // special atoms zone, but don't traverse their outgoing edges, on the
  // assumption that they are shared resources that debuggee is using.
  // Symbols are always allocated in the atoms zone, even if they were
  // created for exactly one compartment and never shared; this rule will
  // include such nodes in the count.
  const Node& referent = edge.referent;
  Zone* zone = referent.zone();

  if (census.targetZones.count() == 0 || census.targetZones.has(zone)) {
    return rootCount->count(mallocSizeOf, referent);
  }

  if (zone && zone->isAtomsZone()) {
    traversal.abandonReferent();
    return rootCount->count(mallocSizeOf, referent);
  }

  traversal.abandonReferent();
  return true;
}

/*** Parsing Breakdowns *****************************************************/

static CountTypePtr ParseChildBreakdown(
    JSContext* cx, HandleObject breakdown, PropertyName* prop,
    MutableHandle<GCVector<JSLinearString*>> seen) {
  RootedValue v(cx);
  if (!GetProperty(cx, breakdown, breakdown, prop, &v)) {
    return nullptr;
  }
  return ParseBreakdown(cx, v, seen);
}

JS_PUBLIC_API CountTypePtr
ParseBreakdown(JSContext* cx, HandleValue breakdownValue,
               MutableHandle<GCVector<JSLinearString*>> seen) {
  if (breakdownValue.isUndefined()) {
    // Construct the default type, { by: 'count' }
    CountTypePtr simple(cx->new_<SimpleCount>());
    return simple;
  }

  RootedObject breakdown(cx, ToObject(cx, breakdownValue));
  if (!breakdown) {
    return nullptr;
  }

  RootedValue byValue(cx);
  if (!GetProperty(cx, breakdown, breakdown, cx->names().by, &byValue)) {
    return nullptr;
  }
  RootedString byString(cx, ToString(cx, byValue));
  if (!byString) {
    return nullptr;
  }
  Rooted<JSLinearString*> by(cx, byString->ensureLinear(cx));
  if (!by) {
    return nullptr;
  }

  for (auto candidate : seen.get()) {
    if (EqualStrings(by, candidate)) {
      UniqueChars byBytes = QuoteString(cx, by, '"');
      if (!byBytes) {
        return nullptr;
      }

      JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                JSMSG_DEBUG_CENSUS_BREAKDOWN_NESTED,
                                byBytes.get());
      return nullptr;
    }
  }
  if (!seen.append(by)) {
    return nullptr;
  }
  auto popper = mozilla::MakeScopeExit([&]() { seen.popBack(); });

  if (StringEqualsLiteral(by, "count")) {
    RootedValue countValue(cx), bytesValue(cx);
    if (!GetProperty(cx, breakdown, breakdown, cx->names().count,
                     &countValue) ||
        !GetProperty(cx, breakdown, breakdown, cx->names().bytes, &bytesValue))
      return nullptr;

    // Both 'count' and 'bytes' default to true if omitted, but ToBoolean
    // naturally treats 'undefined' as false; fix this up.
    if (countValue.isUndefined()) countValue.setBoolean(true);
    if (bytesValue.isUndefined()) bytesValue.setBoolean(true);

    // Undocumented feature, for testing: { by: 'count' } breakdowns can have
    // a 'label' property whose value is converted to a string and included as
    // a 'label' property on the report object.
    RootedValue label(cx);
    if (!GetProperty(cx, breakdown, breakdown, cx->names().label, &label)) {
      return nullptr;
    }

    UniqueTwoByteChars labelUnique(nullptr);
    if (!label.isUndefined()) {
      RootedString labelString(cx, ToString(cx, label));
      if (!labelString) {
        return nullptr;
      }

      labelUnique = JS_CopyStringCharsZ(cx, labelString);
      if (!labelUnique) {
        return nullptr;
      }
    }

    CountTypePtr simple(cx->new_<SimpleCount>(
        labelUnique, ToBoolean(countValue), ToBoolean(bytesValue)));
    return simple;
  }

  if (StringEqualsLiteral(by, "bucket")) {
    return CountTypePtr(cx->new_<BucketCount>());
  }

  if (StringEqualsLiteral(by, "objectClass")) {
    CountTypePtr thenType(
        ParseChildBreakdown(cx, breakdown, cx->names().then, seen));
    if (!thenType) {
      return nullptr;
    }

    CountTypePtr otherType(
        ParseChildBreakdown(cx, breakdown, cx->names().other, seen));
    if (!otherType) {
      return nullptr;
    }

    return CountTypePtr(cx->new_<ByObjectClass>(thenType, otherType));
  }

  if (StringEqualsLiteral(by, "coarseType")) {
    CountTypePtr objectsType(
        ParseChildBreakdown(cx, breakdown, cx->names().objects, seen));
    if (!objectsType) {
      return nullptr;
    }
    CountTypePtr scriptsType(
        ParseChildBreakdown(cx, breakdown, cx->names().scripts, seen));
    if (!scriptsType) {
      return nullptr;
    }
    CountTypePtr stringsType(
        ParseChildBreakdown(cx, breakdown, cx->names().strings, seen));
    if (!stringsType) {
      return nullptr;
    }
    CountTypePtr otherType(
        ParseChildBreakdown(cx, breakdown, cx->names().other, seen));
    if (!otherType) {
      return nullptr;
    }
    CountTypePtr domNodeType(
        ParseChildBreakdown(cx, breakdown, cx->names().domNode, seen));
    if (!domNodeType) {
      return nullptr;
    }

    return CountTypePtr(cx->new_<ByCoarseType>(
        objectsType, scriptsType, stringsType, otherType, domNodeType));
  }

  if (StringEqualsLiteral(by, "internalType")) {
    CountTypePtr thenType(
        ParseChildBreakdown(cx, breakdown, cx->names().then, seen));
    if (!thenType) {
      return nullptr;
    }

    return CountTypePtr(cx->new_<ByUbinodeType>(thenType));
  }

  if (StringEqualsLiteral(by, "descriptiveType")) {
    CountTypePtr thenType(
        ParseChildBreakdown(cx, breakdown, cx->names().then, seen));
    if (!thenType) {
      return nullptr;
    }
    return CountTypePtr(cx->new_<ByDomObjectClass>(thenType));
  }

  if (StringEqualsLiteral(by, "allocationStack")) {
    CountTypePtr thenType(
        ParseChildBreakdown(cx, breakdown, cx->names().then, seen));
    if (!thenType) {
      return nullptr;
    }
    CountTypePtr noStackType(
        ParseChildBreakdown(cx, breakdown, cx->names().noStack, seen));
    if (!noStackType) {
      return nullptr;
    }

    return CountTypePtr(cx->new_<ByAllocationStack>(thenType, noStackType));
  }

  if (StringEqualsLiteral(by, "filename")) {
    CountTypePtr thenType(
        ParseChildBreakdown(cx, breakdown, cx->names().then, seen));
    if (!thenType) {
      return nullptr;
    }

    CountTypePtr noFilenameType(
        ParseChildBreakdown(cx, breakdown, cx->names().noFilename, seen));
    if (!noFilenameType) {
      return nullptr;
    }

    return CountTypePtr(
        cx->new_<ByFilename>(std::move(thenType), std::move(noFilenameType)));
  }

  // We didn't recognize the breakdown type; complain.
  UniqueChars byBytes = QuoteString(cx, by, '"');
  if (!byBytes) {
    return nullptr;
  }

  JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                            JSMSG_DEBUG_CENSUS_BREAKDOWN, byBytes.get());
  return nullptr;
}

// Get the default census breakdown:
//
// { by: "coarseType",
//   objects: { by: "objectClass" },
//   other:   { by: "internalType" },
//   domNode: { by: "descriptiveType" }
// }
static CountTypePtr GetDefaultBreakdown(JSContext* cx) {
  CountTypePtr byDomClass(cx->new_<SimpleCount>());
  if (!byDomClass) {
    return nullptr;
  }
  CountTypePtr byClass(cx->new_<SimpleCount>());
  if (!byClass) {
    return nullptr;
  }

  CountTypePtr byClassElse(cx->new_<SimpleCount>());
  if (!byClassElse) {
    return nullptr;
  }

  CountTypePtr objects(cx->new_<ByObjectClass>(byClass, byClassElse));
  if (!objects) {
    return nullptr;
  }

  CountTypePtr scripts(cx->new_<SimpleCount>());
  if (!scripts) {
    return nullptr;
  }

  CountTypePtr strings(cx->new_<SimpleCount>());
  if (!strings) {
    return nullptr;
  }

  CountTypePtr byType(cx->new_<SimpleCount>());
  if (!byType) {
    return nullptr;
  }

  CountTypePtr other(cx->new_<ByUbinodeType>(byType));
  if (!other) {
    return nullptr;
  }
  CountTypePtr domNode(cx->new_<ByDomObjectClass>(byDomClass));
  if (!domNode) {
    return nullptr;
  }

  return CountTypePtr(
      cx->new_<ByCoarseType>(objects, scripts, strings, other, domNode));
}

JS_PUBLIC_API bool ParseCensusOptions(JSContext* cx, Census& census,
                                      HandleObject options,
                                      CountTypePtr& outResult) {
  RootedValue breakdown(cx, UndefinedValue());
  if (options &&
      !GetProperty(cx, options, options, cx->names().breakdown, &breakdown)) {
    return false;
  }

  Rooted<GCVector<JSLinearString*>> seen(cx, cx);
  outResult = breakdown.isUndefined() ? GetDefaultBreakdown(cx)
                                      : ParseBreakdown(cx, breakdown, &seen);
  return !!outResult;
}

}  // namespace ubi
}  // namespace JS

Messung V0.5
C=87 H=97 G=91

¤ Dauer der Verarbeitung: 0.22 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.