/* -*- 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 "mozilla/DebugOnly.h"

#include "gc/GCInternals.h"
#include "gc/GCLock.h"
#include "js/HashTable.h"
#include "vm/Realm.h"
#include "vm/Runtime.h"

#include "gc/PrivateIterators-inl.h"
#include "vm/JSContext-inl.h"

using namespace js;
using namespace js::gc;

static void IterateRealmsArenasCellsUnbarriered(
    JSContext* cx, Zone* zone, void* data,
    JS::IterateRealmCallback realmCallback, IterateArenaCallback arenaCallback,
    IterateCellCallback cellCallback) {
  {
    Rooted<Realm*> realm(cx);
    for (RealmsInZoneIter r(zone); !r.done(); r.next()) {
      realm = r;
      (*realmCallback)(cx, data, realm);
    }
  }

  for (auto thingKind : AllAllocKinds()) {
    JS::TraceKind traceKind = MapAllocToTraceKind(thingKind);
    size_t thingSize = Arena::thingSize(thingKind);

    for (ArenaIter aiter(zone, thingKind); !aiter.done(); aiter.next()) {
      Arena* arena = aiter.get();
      (*arenaCallback)(cx->runtime(), data, arena, traceKind, thingSize);
      for (ArenaCellIter iter(arena); !iter.done(); iter.next()) {
        (*cellCallback)(cx->runtime(), data,
                        JS::GCCellPtr(iter.getCell(), traceKind), thingSize);
      }
    }
  }
}

void js::IterateHeapUnbarriered(JSContext* cx, void* data,
                                IterateZoneCallback zoneCallback,
                                JS::IterateRealmCallback realmCallback,
                                IterateArenaCallback arenaCallback,
                                IterateCellCallback cellCallback) {
  AutoPrepareForTracing prep(cx);

  for (ZonesIter zone(cx->runtime(), WithAtoms); !zone.done(); zone.next()) {
    (*zoneCallback)(cx->runtime(), data, zone);
    IterateRealmsArenasCellsUnbarriered(cx, zone, data, realmCallback,
                                        arenaCallback, cellCallback);
  }
}

void js::IterateHeapUnbarrieredForZone(JSContext* cx, Zone* zone, void* data,
                                       IterateZoneCallback zoneCallback,
                                       JS::IterateRealmCallback realmCallback,
                                       IterateArenaCallback arenaCallback,
                                       IterateCellCallback cellCallback) {
  AutoPrepareForTracing prep(cx);

  (*zoneCallback)(cx->runtime(), data, zone);
  IterateRealmsArenasCellsUnbarriered(cx, zone, data, realmCallback,
                                      arenaCallback, cellCallback);
}

void js::IterateChunks(JSContext* cx, void* data,
                       IterateChunkCallback chunkCallback) {
  AutoPrepareForTracing prep(cx);
  AutoLockGC lock(cx->runtime());

  for (auto chunk = cx->runtime()->gc.allNonEmptyChunks(lock); !chunk.done();
       chunk.next()) {
    chunkCallback(cx->runtime(), data, chunk);
  }
}

static void TraverseInnerLazyScriptsForLazyScript(
    JSContext* cx, void* data, BaseScript* enclosingScript,
    IterateScriptCallback lazyScriptCallback, const JS::AutoRequireNoGC& nogc) {
  for (JS::GCCellPtr gcThing : enclosingScript->gcthings()) {
    if (!gcThing.is<JSObject>()) {
      continue;
    }
    JSObject* obj = &gcThing.as<JSObject>();

    MOZ_ASSERT(obj->is<JSFunction>(),
               "All objects in lazy scripts should be functions");
    JSFunction* fun = &obj->as<JSFunction>();

    if (!fun->hasBaseScript() || fun->hasBytecode()) {
      continue;
    }

    BaseScript* script = fun->baseScript();
    MOZ_ASSERT_IF(script->hasEnclosingScript(),
                  script->enclosingScript() == enclosingScript);

    lazyScriptCallback(cx->runtime(), data, script, nogc);

    TraverseInnerLazyScriptsForLazyScript(cx, data, script, lazyScriptCallback,
                                          nogc);
  }
}

static inline void DoScriptCallback(JSContext* cx, void* data,
                                    BaseScript* script,
                                    IterateScriptCallback callback,
                                    const JS::AutoRequireNoGC& nogc) {
  // Exclude any scripts that may be the result of a failed compile. Check that
  // script either has bytecode or is ready to delazify.
  //
  // This excludes lazy scripts that do not have an enclosing scope because we
  // cannot distinguish a failed compile fragment from a lazy script with a lazy
  // parent.
  if (!script->hasBytecode() && !script->isReadyForDelazification()) {
    return;
  }

  // Invoke callback.
  callback(cx->runtime(), data, script, nogc);

  // The check above excluded lazy scripts with lazy parents, so explicitly
  // visit inner scripts now if we are lazy with a successfully compiled parent.
  if (!script->hasBytecode()) {
    TraverseInnerLazyScriptsForLazyScript(cx, data, script, callback, nogc);
  }
}

template <bool HasBytecode>
static void IterateScriptsImpl(JSContext* cx, Realm* realm, void* data,
                               IterateScriptCallback scriptCallback) {
  MOZ_ASSERT(!cx->suppressGC);
  AutoEmptyNurseryAndPrepareForTracing prep(cx);
  JS::AutoSuppressGCAnalysis nogc;

  if (realm) {
    Zone* zone = realm->zone();
    for (auto iter = zone->cellIter<BaseScript>(prep); !iter.done();
         iter.next()) {
      if (iter->realm() != realm) {
        continue;
      }
      if (HasBytecode != iter->hasBytecode()) {
        continue;
      }
      DoScriptCallback(cx, data, iter.get(), scriptCallback, nogc);
    }
  } else {
    for (ZonesIter zone(cx->runtime(), SkipAtoms); !zone.done(); zone.next()) {
      for (auto iter = zone->cellIter<BaseScript>(prep); !iter.done();
           iter.next()) {
        if (HasBytecode != iter->hasBytecode()) {
          continue;
        }
        DoScriptCallback(cx, data, iter.get(), scriptCallback, nogc);
      }
    }
  }
}

void js::IterateScripts(JSContext* cx, Realm* realm, void* data,
                        IterateScriptCallback scriptCallback) {
  IterateScriptsImpl</*HasBytecode = */ true>(cx, realm, data, scriptCallback);
}

void js::IterateLazyScripts(JSContext* cx, Realm* realm, void* data,
                            IterateScriptCallback scriptCallback) {
  IterateScriptsImpl</*HasBytecode = */ false>(cx, realm, data, scriptCallback);
}

void js::IterateGrayObjects(Zone* zone, GCThingCallback cellCallback,
                            void* data) {
  MOZ_ASSERT(!JS::RuntimeHeapIsBusy());

  AutoPrepareForTracing prep(TlsContext.get());
  for (auto kind : ObjectAllocKinds()) {
    for (GrayObjectIter obj(zone, kind); !obj.done(); obj.next()) {
      if (obj->asTenured().isMarkedGray()) {
        cellCallback(data, JS::GCCellPtr(obj.get()));
      }
    }
  }
}

JS_PUBLIC_API void JS_IterateCompartments(
    JSContext* cx, void* data,
    JSIterateCompartmentCallback compartmentCallback) {
  AutoTraceSession session(cx->runtime());

  for (CompartmentsIter c(cx->runtime()); !c.done(); c.next()) {
    if ((*compartmentCallback)(cx, data, c) ==
        JS::CompartmentIterResult::Stop) {
      break;
    }
  }
}

JS_PUBLIC_API void JS_IterateCompartmentsInZone(
    JSContext* cx, JS::Zone* zone, void* data,
    JSIterateCompartmentCallback compartmentCallback) {
  AutoTraceSession session(cx->runtime());

  for (CompartmentsInZoneIter c(zone); !c.done(); c.next()) {
    if ((*compartmentCallback)(cx, data, c) ==
        JS::CompartmentIterResult::Stop) {
      break;
    }
  }
}

JS_PUBLIC_API void JS::IterateRealms(JSContext* cx, void* data,
                                     JS::IterateRealmCallback realmCallback) {
  AutoTraceSession session(cx->runtime());

  Rooted<Realm*> realm(cx);
  for (RealmsIter r(cx->runtime()); !r.done(); r.next()) {
    realm = r;
    (*realmCallback)(cx, data, realm);
  }
}

JS_PUBLIC_API void JS::IterateRealmsWithPrincipals(
    JSContext* cx, JSPrincipals* principals, void* data,
    JS::IterateRealmCallback realmCallback) {
  MOZ_ASSERT(principals);

  AutoTraceSession session(cx->runtime());

  Rooted<Realm*> realm(cx);
  for (RealmsIter r(cx->runtime()); !r.done(); r.next()) {
    if (r->principals() != principals) {
      continue;
    }
    realm = r;
    (*realmCallback)(cx, data, realm);
  }
}

JS_PUBLIC_API void JS::IterateRealmsInCompartment(
    JSContext* cx, JS::Compartment* compartment, void* data,
    JS::IterateRealmCallback realmCallback) {
  AutoTraceSession session(cx->runtime());

  Rooted<Realm*> realm(cx);
  for (RealmsInCompartmentIter r(compartment); !r.done(); r.next()) {
    realm = r;
    (*realmCallback)(cx, data, realm);
  }
}
