/* 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 "vm/FrameIter-inl.h"

#include "mozilla/Assertions.h"  // MOZ_ASSERT, MOZ_CRASH
#include "mozilla/MaybeOneOf.h"  // mozilla::MaybeOneOf

#include <stddef.h>  // size_t
#include <stdint.h>  // uint8_t, uint32_t
#include <stdlib.h>  // getenv

#include "jit/BaselineFrame.h"   // js::jit::BaselineFrame
#include "jit/JitFrames.h"       // js::jit::EnsureUnwoundJitExitFrame
#include "jit/JSJitFrameIter.h"  // js::jit::{FrameType,InlineFrameIterator,JSJitFrameIter,MaybeReadFallback,SnapshotIterator}
#include "js/ColumnNumber.h"  // JS::LimitedColumnNumberOneOrigin, JS::TaggedColumnNumberOneOrigin
#include "js/GCAPI.h"              // JS::AutoSuppressGCAnalysis
#include "js/Principals.h"         // JSSubsumesOp
#include "vm/Activation.h"         // js::Activation{,Iterator}
#include "vm/EnvironmentObject.h"  // js::CallObject
#include "vm/JitActivation.h"      // js::jit::JitActivation
#include "vm/JSContext.h"          // JSContext
#include "vm/JSFunction.h"         // JSFunction
#include "vm/JSScript.h"  // js::PCToLineNumber, JSScript, js::ScriptSource
#include "vm/Runtime.h"   // JSRuntime
#include "vm/Stack.h"  // js::{AbstractFramePtr,InterpreterFrame,MaybeCheckAliasing}
#include "wasm/WasmFrameIter.h"  // js::wasm::WasmFrameIter
#include "wasm/WasmInstance.h"   // js::wasm::Instance

#include "jit/JSJitFrameIter-inl.h"  // js::jit::JSJitFrameIter::baselineFrame{,NumValueSlots}
#include "vm/Stack-inl.h"  // js::AbstractFramePtr::*

namespace JS {
class JS_PUBLIC_API Realm;
}  // namespace JS

namespace js {
class ArgumentsObject;
}  // namespace js

using JS::Realm;
using JS::Value;

using js::AbstractFramePtr;
using js::ArgumentsObject;
using js::CallObject;
using js::FrameIter;
using js::JitFrameIter;
using js::NonBuiltinFrameIter;
using js::NonBuiltinScriptFrameIter;
using js::OnlyJSJitFrameIter;
using js::ScriptSource;
using js::jit::JSJitFrameIter;

JitFrameIter::JitFrameIter(const JitFrameIter& another) { *this = another; }

JitFrameIter& JitFrameIter::operator=(const JitFrameIter& another) {
  MOZ_ASSERT(this != &another);

  act_ = another.act_;
  mustUnwindActivation_ = another.mustUnwindActivation_;

  if (isSome()) {
    iter_.destroy();
  }
  if (!another.isSome()) {
    return *this;
  }

  if (another.isJSJit()) {
    iter_.construct<jit::JSJitFrameIter>(another.asJSJit());
  } else {
    MOZ_ASSERT(another.isWasm());
    iter_.construct<wasm::WasmFrameIter>(another.asWasm());
  }

  return *this;
}

JitFrameIter::JitFrameIter(jit::JitActivation* act, bool mustUnwindActivation) {
  act_ = act;
  mustUnwindActivation_ = mustUnwindActivation;
  MOZ_ASSERT(act->hasExitFP(),
             "packedExitFP is used to determine if JSJit or wasm");
  if (act->hasJSExitFP()) {
    iter_.construct<jit::JSJitFrameIter>(act);
  } else {
    MOZ_ASSERT(act->hasWasmExitFP());
    iter_.construct<wasm::WasmFrameIter>(act);
  }
  settle();
}

void JitFrameIter::skipNonScriptedJSFrames() {
  if (isJSJit()) {
    // Stop at the first scripted frame.
    jit::JSJitFrameIter& frames = asJSJit();
    while (!frames.isScripted() && !frames.done()) {
      ++frames;
    }
    settle();
  }
}

bool JitFrameIter::isSelfHostedIgnoringInlining() const {
  MOZ_ASSERT(!done());

  if (isWasm()) {
    return false;
  }

  return asJSJit().script()->selfHosted();
}

JS::Realm* JitFrameIter::realm() const {
  MOZ_ASSERT(!done());

  if (isWasm()) {
    return asWasm().instance()->realm();
  }

  if (asJSJit().isScripted()) {
    return asJSJit().script()->realm();
  }

  MOZ_RELEASE_ASSERT(asJSJit().isTrampolineNative());
  return asJSJit().callee()->realm();
}

uint8_t* JitFrameIter::resumePCinCurrentFrame() const {
  if (isWasm()) {
    return asWasm().resumePCinCurrentFrame();
  }
  return asJSJit().resumePCinCurrentFrame();
}

bool JitFrameIter::done() const {
  if (!isSome()) {
    return true;
  }
  if (isJSJit()) {
    return asJSJit().done();
  }
  if (isWasm()) {
    return asWasm().done();
  }
  MOZ_CRASH("unhandled case");
}

void JitFrameIter::settle() {
  if (isJSJit()) {
    const jit::JSJitFrameIter& jitFrame = asJSJit();
    if (jitFrame.type() != jit::FrameType::WasmToJSJit) {
      return;
    }

    // Transition from js jit frames to wasm frames: we're on the
    // wasm-to-jit fast path. The current stack layout is as follows:
    // (stack grows downward)
    //
    // [--------------------]
    // [WASM FUNC           ]
    // [WASM JIT EXIT FRAME ]
    // [JIT WASM ENTRY FRAME] <-- we're here.
    //
    // So prevFP points to the wasm jit exit FP, maintaing the invariant in
    // WasmFrameIter that the first frame is an exit frame and can be
    // popped.

    wasm::Frame* prevFP = (wasm::Frame*)jitFrame.prevFp();

    if (mustUnwindActivation_) {
      // The JS-JIT exit frame doesn't support stack switching and will only be
      // used if original wasm func was on the main stack.
#ifdef ENABLE_WASM_JSPI
      MOZ_ASSERT(!act_->cx()->wasm().findStackForAddress(
          act_->cx(), reinterpret_cast<uintptr_t>(prevFP->wasmCaller())));
#endif
      act_->setWasmExitFP(prevFP);
    }

    iter_.destroy();
    iter_.construct<wasm::WasmFrameIter>(act_, prevFP);
    MOZ_ASSERT(!asWasm().done());
    return;
  }

  if (isWasm()) {
    const wasm::WasmFrameIter& wasmFrame = asWasm();
    if (!wasmFrame.done() || !wasmFrame.unwoundCallerFPIsJSJit()) {
      return;
    }

    // Transition from wasm frames to jit frames: we're on the
    // jit-to-wasm fast path. The current stack layout is as follows:
    // (stack grows downward)
    //
    // [--------------------]
    // [JIT FRAME           ]
    // [WASM JIT ENTRY FRAME] <-- we're here
    //
    // The wasm iterator has saved the previous jit frame pointer for us.

    uint8_t* prevFP = wasmFrame.unwoundCallerFP();

    if (mustUnwindActivation_) {
      act_->setJSExitFP(prevFP);
    }

    iter_.destroy();
    iter_.construct<jit::JSJitFrameIter>(act_, prevFP, mustUnwindActivation_);
    MOZ_ASSERT(!asJSJit().done());
    return;
  }
}

void JitFrameIter::operator++() {
  MOZ_ASSERT(isSome());
  if (isJSJit()) {
    const jit::JSJitFrameIter& jitFrame = asJSJit();

    jit::JitFrameLayout* prevFrame = nullptr;
    if (mustUnwindActivation_ && jitFrame.isScripted()) {
      prevFrame = jitFrame.jsFrame();
    }

    ++asJSJit();

    if (prevFrame) {
      // Unwind the frame by updating packedExitFP. This is necessary
      // so that (1) debugger exception unwind and leave frame hooks
      // don't see this frame when they use ScriptFrameIter, and (2)
      // ScriptFrameIter does not crash when accessing an IonScript
      // that's destroyed by the ionScript->decref call.
      EnsureUnwoundJitExitFrame(act_, prevFrame);
    }
  } else if (isWasm()) {
    ++asWasm();
  } else {
    MOZ_CRASH("unhandled case");
  }
  settle();
}

OnlyJSJitFrameIter::OnlyJSJitFrameIter(jit::JitActivation* act)
    : JitFrameIter(act) {
  settle();
}

OnlyJSJitFrameIter::OnlyJSJitFrameIter(const ActivationIterator& iter)
    : OnlyJSJitFrameIter(iter->asJit()) {}

/*****************************************************************************/

void FrameIter::popActivation() {
  ++data_.activations_;
  settleOnActivation();
}

bool FrameIter::principalsSubsumeFrame() const {
  // If the caller supplied principals, only show frames which are
  // subsumed (of the same origin or of an origin accessible) by these
  // principals.

  MOZ_ASSERT(!done());

  if (!data_.principals_) {
    return true;
  }

  JSSubsumesOp subsumes = data_.cx_->runtime()->securityCallbacks->subsumes;
  if (!subsumes) {
    return true;
  }

  JS::AutoSuppressGCAnalysis nogc;
  return subsumes(data_.principals_, realm()->principals());
}

void FrameIter::popInterpreterFrame() {
  MOZ_ASSERT(data_.state_ == INTERP);

  ++data_.interpFrames_;

  if (data_.interpFrames_.done()) {
    popActivation();
  } else {
    data_.pc_ = data_.interpFrames_.pc();
  }
}

void FrameIter::settleOnActivation() {
  MOZ_ASSERT(!data_.cx_->inUnsafeCallWithABI);

  while (true) {
    if (data_.activations_.done()) {
      data_.state_ = DONE;
      return;
    }

    Activation* activation = data_.activations_.activation();

    if (activation->isJit()) {
      data_.jitFrames_ = JitFrameIter(activation->asJit());
      data_.jitFrames_.skipNonScriptedJSFrames();
      if (data_.jitFrames_.done()) {
        // It's possible to have an JitActivation with no scripted
        // frames, for instance if we hit an over-recursion during
        // bailout.
        ++data_.activations_;
        continue;
      }
      data_.state_ = JIT;
      nextJitFrame();
      return;
    }

    MOZ_ASSERT(activation->isInterpreter());

    InterpreterActivation* interpAct = activation->asInterpreter();
    data_.interpFrames_ = InterpreterFrameIterator(interpAct);

    // If we OSR'ed into JIT code, skip the interpreter frame so that
    // the same frame is not reported twice.
    if (data_.interpFrames_.frame()->runningInJit()) {
      ++data_.interpFrames_;
      if (data_.interpFrames_.done()) {
        ++data_.activations_;
        continue;
      }
    }

    MOZ_ASSERT(!data_.interpFrames_.frame()->runningInJit());
    data_.pc_ = data_.interpFrames_.pc();
    data_.state_ = INTERP;
    return;
  }
}

FrameIter::Data::Data(JSContext* cx, DebuggerEvalOption debuggerEvalOption,
                      JSPrincipals* principals)
    : cx_(cx),
      debuggerEvalOption_(debuggerEvalOption),
      principals_(principals),
      state_(DONE),
      pc_(nullptr),
      interpFrames_(nullptr),
      activations_(cx),
      ionInlineFrameNo_(0) {}

FrameIter::Data::Data(const FrameIter::Data& other) = default;

FrameIter::FrameIter(JSContext* cx, DebuggerEvalOption debuggerEvalOption)
    : data_(cx, debuggerEvalOption, nullptr),
      ionInlineFrames_(cx, (js::jit::JSJitFrameIter*)nullptr) {
  settleOnActivation();

  // No principals so we can see all frames.
  MOZ_ASSERT_IF(!done(), principalsSubsumeFrame());
}

FrameIter::FrameIter(JSContext* cx, DebuggerEvalOption debuggerEvalOption,
                     JSPrincipals* principals)
    : data_(cx, debuggerEvalOption, principals),
      ionInlineFrames_(cx, (js::jit::JSJitFrameIter*)nullptr) {
  settleOnActivation();

  // If we're not allowed to see this frame, call operator++ to skip this (and
  // other) cross-origin frames.
  if (!done() && !principalsSubsumeFrame()) {
    ++*this;
  }
}

FrameIter::FrameIter(const Data& data)
    : data_(data),
      ionInlineFrames_(data.cx_, isIonScripted() ? &jsJitFrame() : nullptr) {
  MOZ_ASSERT(data.cx_);
  if (isIonScripted()) {
    while (ionInlineFrames_.frameNo() != data.ionInlineFrameNo_) {
      ++ionInlineFrames_;
    }
  }
}

void FrameIter::nextJitFrame() {
  MOZ_ASSERT(data_.jitFrames_.isSome());

  if (isJSJit()) {
    if (jsJitFrame().isIonScripted()) {
      ionInlineFrames_.resetOn(&jsJitFrame());
      data_.pc_ = ionInlineFrames_.pc();
    } else {
      MOZ_ASSERT(jsJitFrame().isBaselineJS());
      jsJitFrame().baselineScriptAndPc(nullptr, &data_.pc_);
    }
    return;
  }

  MOZ_ASSERT(isWasm());
  wasmFrame().enableInlinedFrames();
  data_.pc_ = nullptr;
}

void FrameIter::popJitFrame() {
  MOZ_ASSERT(data_.state_ == JIT);
  MOZ_ASSERT(data_.jitFrames_.isSome());

  if (isJSJit() && jsJitFrame().isIonScripted() && ionInlineFrames_.more()) {
    ++ionInlineFrames_;
    data_.pc_ = ionInlineFrames_.pc();
    return;
  }

  ++data_.jitFrames_;
  data_.jitFrames_.skipNonScriptedJSFrames();

  if (!data_.jitFrames_.done()) {
    nextJitFrame();
  } else {
    data_.jitFrames_.reset();
    popActivation();
  }
}

FrameIter& FrameIter::operator++() {
  while (true) {
    switch (data_.state_) {
      case DONE:
        MOZ_CRASH("Unexpected state");
      case INTERP:
        if (interpFrame()->isDebuggerEvalFrame() &&
            data_.debuggerEvalOption_ == FOLLOW_DEBUGGER_EVAL_PREV_LINK) {
          AbstractFramePtr eifPrev = interpFrame()->evalInFramePrev();

          popInterpreterFrame();

          while (!hasUsableAbstractFramePtr() ||
                 abstractFramePtr() != eifPrev) {
            if (data_.state_ == JIT) {
              popJitFrame();
            } else {
              popInterpreterFrame();
            }
          }

          break;
        }
        popInterpreterFrame();
        break;
      case JIT:
        popJitFrame();
        break;
    }

    if (done() || principalsSubsumeFrame()) {
      break;
    }
  }

  return *this;
}

mozilla::UniquePtr<FrameIter::Data> FrameIter::copyData() const {
  mozilla::UniquePtr<Data> data(data_.cx_->new_<FrameIter::Data>(data_));
  if (!data) {
    return nullptr;
  }

  if (data && isIonScripted()) {
    data->ionInlineFrameNo_ = ionInlineFrames_.frameNo();
  }
  return data;
}

void* FrameIter::rawFramePtr() const {
  switch (data_.state_) {
    case DONE:
      return nullptr;
    case INTERP:
      return interpFrame();
    case JIT:
      if (isJSJit()) {
        return jsJitFrame().fp();
      }
      MOZ_ASSERT(isWasm());
      return nullptr;
  }
  MOZ_CRASH("Unexpected state");
}

JS::Compartment* FrameIter::compartment() const {
  switch (data_.state_) {
    case DONE:
      break;
    case INTERP:
    case JIT:
      return data_.activations_->compartment();
  }
  MOZ_CRASH("Unexpected state");
}

Realm* FrameIter::realm() const {
  MOZ_ASSERT(!done());

  if (hasScript()) {
    return script()->realm();
  }

  return wasmInstance()->realm();
}

bool FrameIter::isEvalFrame() const {
  switch (data_.state_) {
    case DONE:
      break;
    case INTERP:
      return interpFrame()->isEvalFrame();
    case JIT:
      if (isJSJit()) {
        if (jsJitFrame().isBaselineJS()) {
          return jsJitFrame().baselineFrame()->isEvalFrame();
        }
        MOZ_ASSERT(!script()->isForEval());
        return false;
      }
      MOZ_ASSERT(isWasm());
      return false;
  }
  MOZ_CRASH("Unexpected state");
}

bool FrameIter::isModuleFrame() const {
  MOZ_ASSERT(!done());

  if (hasScript()) {
    return script()->isModule();
  }
  MOZ_CRASH("Unexpected state");
}

bool FrameIter::isFunctionFrame() const {
  MOZ_ASSERT(!done());
  switch (data_.state_) {
    case DONE:
      break;
    case INTERP:
      return interpFrame()->isFunctionFrame();
    case JIT:
      if (isJSJit()) {
        if (jsJitFrame().isBaselineJS()) {
          return jsJitFrame().baselineFrame()->isFunctionFrame();
        }
        return script()->isFunction();
      }
      MOZ_ASSERT(isWasm());
      return false;
  }
  MOZ_CRASH("Unexpected state");
}

JSAtom* FrameIter::maybeFunctionDisplayAtom() const {
  switch (data_.state_) {
    case DONE:
      break;
    case INTERP:
    case JIT:
      if (isWasm()) {
        return wasmFrame().functionDisplayAtom();
      }
      if (isFunctionFrame()) {
        return calleeTemplate()->fullDisplayAtom();
      }
      return nullptr;
  }

  MOZ_CRASH("Unexpected state");
}

ScriptSource* FrameIter::scriptSource() const {
  switch (data_.state_) {
    case DONE:
      break;
    case INTERP:
    case JIT:
      return script()->scriptSource();
  }

  MOZ_CRASH("Unexpected state");
}

const char* FrameIter::filename() const {
  switch (data_.state_) {
    case DONE:
      break;
    case INTERP:
    case JIT:
      if (isWasm()) {
        return wasmFrame().filename();
      }
      return script()->filename();
  }

  MOZ_CRASH("Unexpected state");
}

const char16_t* FrameIter::displayURL() const {
  switch (data_.state_) {
    case DONE:
      break;
    case INTERP:
    case JIT:
      if (isWasm()) {
        return nullptr;
      }
      ScriptSource* ss = script()->scriptSource();
      return ss->hasDisplayURL() ? ss->displayURL() : nullptr;
  }
  MOZ_CRASH("Unexpected state");
}

unsigned FrameIter::computeLine(JS::TaggedColumnNumberOneOrigin* column) const {
  switch (data_.state_) {
    case DONE:
      break;
    case INTERP:
    case JIT:
      if (isWasm()) {
        return wasmFrame().computeLine(column);
      }
      JS::LimitedColumnNumberOneOrigin columnNumber;
      unsigned lineNumber = PCToLineNumber(script(), pc(), &columnNumber);
      if (column) {
        *column = JS::TaggedColumnNumberOneOrigin(columnNumber);
      }
      return lineNumber;
  }

  MOZ_CRASH("Unexpected state");
}

bool FrameIter::mutedErrors() const {
  switch (data_.state_) {
    case DONE:
      break;
    case INTERP:
    case JIT:
      if (isWasm()) {
        return false;
      }
      return script()->mutedErrors();
  }
  MOZ_CRASH("Unexpected state");
}

bool FrameIter::isConstructing() const {
  switch (data_.state_) {
    case DONE:
      break;
    case JIT:
      MOZ_ASSERT(isJSJit());
      if (jsJitFrame().isIonScripted()) {
        return ionInlineFrames_.isConstructing();
      }
      MOZ_ASSERT(jsJitFrame().isBaselineJS());
      return jsJitFrame().isConstructing();
    case INTERP:
      return interpFrame()->isConstructing();
  }

  MOZ_CRASH("Unexpected state");
}

bool FrameIter::ensureHasRematerializedFrame(JSContext* cx) {
  MOZ_ASSERT(isIon());
  return !!activation()->asJit()->getRematerializedFrame(cx, jsJitFrame());
}

bool FrameIter::hasUsableAbstractFramePtr() const {
  switch (data_.state_) {
    case DONE:
      return false;
    case JIT:
      if (isJSJit()) {
        if (jsJitFrame().isBaselineJS()) {
          return true;
        }

        MOZ_ASSERT(jsJitFrame().isIonScripted());
        return !!activation()->asJit()->lookupRematerializedFrame(
            jsJitFrame().fp(), ionInlineFrames_.frameNo());
      }
      MOZ_ASSERT(isWasm());
      return wasmFrame().debugEnabled();
    case INTERP:
      return true;
  }
  MOZ_CRASH("Unexpected state");
}

AbstractFramePtr FrameIter::abstractFramePtr() const {
  MOZ_ASSERT(hasUsableAbstractFramePtr());
  switch (data_.state_) {
    case DONE:
      break;
    case JIT: {
      if (isJSJit()) {
        if (jsJitFrame().isBaselineJS()) {
          return jsJitFrame().baselineFrame();
        }
        MOZ_ASSERT(isIonScripted());
        return activation()->asJit()->lookupRematerializedFrame(
            jsJitFrame().fp(), ionInlineFrames_.frameNo());
      }
      MOZ_ASSERT(isWasm());
      MOZ_ASSERT(wasmFrame().debugEnabled());
      return wasmFrame().debugFrame();
    }
    case INTERP:
      MOZ_ASSERT(interpFrame());
      return AbstractFramePtr(interpFrame());
  }
  MOZ_CRASH("Unexpected state");
}

void FrameIter::updatePcQuadratic() {
  switch (data_.state_) {
    case DONE:
      break;
    case INTERP: {
      InterpreterFrame* frame = interpFrame();
      InterpreterActivation* activation = data_.activations_->asInterpreter();

      // Look for the current frame.
      data_.interpFrames_ = InterpreterFrameIterator(activation);
      while (data_.interpFrames_.frame() != frame) {
        ++data_.interpFrames_;
      }

      // Update the pc.
      MOZ_ASSERT(data_.interpFrames_.frame() == frame);
      data_.pc_ = data_.interpFrames_.pc();
      return;
    }
    case JIT:
      if (jsJitFrame().isBaselineJS()) {
        jit::BaselineFrame* frame = jsJitFrame().baselineFrame();
        jit::JitActivation* activation = data_.activations_->asJit();

        // activation's exitFP may be invalid, so create a new
        // activation iterator.
        data_.activations_ = ActivationIterator(data_.cx_);
        while (data_.activations_.activation() != activation) {
          ++data_.activations_;
        }

        // Look for the current frame.
        data_.jitFrames_ = JitFrameIter(data_.activations_->asJit());
        while (!isJSJit() || !jsJitFrame().isBaselineJS() ||
               jsJitFrame().baselineFrame() != frame) {
          ++data_.jitFrames_;
        }

        // Update the pc.
        MOZ_ASSERT(jsJitFrame().baselineFrame() == frame);
        jsJitFrame().baselineScriptAndPc(nullptr, &data_.pc_);
        return;
      }
      break;
  }
  MOZ_CRASH("Unexpected state");
}

void FrameIter::wasmUpdateBytecodeOffset() {
  MOZ_RELEASE_ASSERT(isWasm(), "Unexpected state");

  wasm::DebugFrame* frame = wasmFrame().debugFrame();

  // Relookup the current frame, updating the bytecode offset in the process.
  data_.jitFrames_ = JitFrameIter(data_.activations_->asJit());
  while (!isWasm() || !wasmFrame().debugEnabled() ||
         wasmFrame().debugFrame() != frame) {
    ++data_.jitFrames_;
  }

  MOZ_ASSERT(wasmFrame().debugFrame() == frame);
}

JSFunction* FrameIter::calleeTemplate() const {
  switch (data_.state_) {
    case DONE:
      break;
    case INTERP:
      MOZ_ASSERT(isFunctionFrame());
      return &interpFrame()->callee();
    case JIT:
      if (jsJitFrame().isBaselineJS()) {
        return jsJitFrame().callee();
      }
      MOZ_ASSERT(jsJitFrame().isIonScripted());
      return ionInlineFrames_.calleeTemplate();
  }
  MOZ_CRASH("Unexpected state");
}

JSFunction* FrameIter::callee(JSContext* cx) const {
  switch (data_.state_) {
    case DONE:
      break;
    case INTERP:
      return calleeTemplate();
    case JIT:
      if (isIonScripted()) {
        jit::MaybeReadFallback recover(cx, activation()->asJit(),
                                       &jsJitFrame());
        return ionInlineFrames_.callee(recover);
      }
      MOZ_ASSERT(jsJitFrame().isBaselineJS());
      return calleeTemplate();
  }
  MOZ_CRASH("Unexpected state");
}

bool FrameIter::matchCallee(JSContext* cx, JS::Handle<JSFunction*> fun) const {
  // Use the calleeTemplate to rule out a match without needing to invalidate to
  // find the actual callee. The real callee my be a clone of the template which
  // should *not* be considered a match.
  JSFunction* currentCallee = calleeTemplate();

  if (currentCallee->nargs() != fun->nargs()) {
    return false;
  }

  if (currentCallee->flags().stableAcrossClones() !=
      fun->flags().stableAcrossClones()) {
    return false;
  }

  // The calleeTemplate for a callee will always have the same BaseScript. If
  // the script clones do not use the same script, they also have a different
  // group and Ion will not inline them interchangeably.
  //
  // See: js::jit::InlineFrameIterator::findNextFrame()
  if (currentCallee->hasBaseScript() && fun->hasBaseScript()) {
    if (currentCallee->baseScript() != fun->baseScript()) {
      return false;
    }
  }

  return callee(cx) == fun;
}

unsigned FrameIter::numActualArgs() const {
  switch (data_.state_) {
    case DONE:
      break;
    case INTERP:
      MOZ_ASSERT(isFunctionFrame());
      return interpFrame()->numActualArgs();
    case JIT:
      if (isIonScripted()) {
        return ionInlineFrames_.numActualArgs();
      }
      MOZ_ASSERT(jsJitFrame().isBaselineJS());
      return jsJitFrame().numActualArgs();
  }
  MOZ_CRASH("Unexpected state");
}

unsigned FrameIter::numFormalArgs() const {
  return script()->function()->nargs();
}

Value FrameIter::unaliasedActual(unsigned i,
                                 MaybeCheckAliasing checkAliasing) const {
  return abstractFramePtr().unaliasedActual(i, checkAliasing);
}

JSObject* FrameIter::environmentChain(JSContext* cx) const {
  switch (data_.state_) {
    case DONE:
      break;
    case JIT:
      if (isJSJit()) {
        if (isIonScripted()) {
          jit::MaybeReadFallback recover(cx, activation()->asJit(),
                                         &jsJitFrame());
          return ionInlineFrames_.environmentChain(recover);
        }
        return jsJitFrame().baselineFrame()->environmentChain();
      }
      MOZ_ASSERT(isWasm());
      return wasmFrame().debugFrame()->environmentChain();
    case INTERP:
      return interpFrame()->environmentChain();
  }
  MOZ_CRASH("Unexpected state");
}

bool FrameIter::hasInitialEnvironment(JSContext* cx) const {
  if (hasUsableAbstractFramePtr()) {
    return abstractFramePtr().hasInitialEnvironment();
  }

  if (isWasm()) {
    // See JSFunction::needsFunctionEnvironmentObjects().
    return false;
  }

  MOZ_ASSERT(isJSJit());
  MOZ_ASSERT(isIonScripted());

  bool hasInitialEnv = false;
  jit::MaybeReadFallback recover(cx, activation()->asJit(), &jsJitFrame());
  ionInlineFrames_.environmentChain(recover, &hasInitialEnv);

  return hasInitialEnv;
}

CallObject& FrameIter::callObj(JSContext* cx) const {
  MOZ_ASSERT(calleeTemplate()->needsCallObject());
  MOZ_ASSERT(hasInitialEnvironment(cx));

  JSObject* pobj = environmentChain(cx);
  while (!pobj->is<CallObject>()) {
    pobj = pobj->enclosingEnvironment();
  }
  return pobj->as<CallObject>();
}

bool FrameIter::hasArgsObj() const { return abstractFramePtr().hasArgsObj(); }

ArgumentsObject& FrameIter::argsObj() const {
  MOZ_ASSERT(hasArgsObj());
  return abstractFramePtr().argsObj();
}

Value FrameIter::thisArgument(JSContext* cx) const {
  MOZ_ASSERT(isFunctionFrame());

  switch (data_.state_) {
    case DONE:
      break;
    case JIT:
      if (isIonScripted()) {
        jit::MaybeReadFallback recover(cx, activation()->asJit(),
                                       &jsJitFrame());
        return ionInlineFrames_.thisArgument(recover);
      }
      return jsJitFrame().baselineFrame()->thisArgument();
    case INTERP:
      return interpFrame()->thisArgument();
  }
  MOZ_CRASH("Unexpected state");
}

Value FrameIter::returnValue() const {
  switch (data_.state_) {
    case DONE:
      break;
    case JIT:
      if (jsJitFrame().isBaselineJS()) {
        return jsJitFrame().baselineFrame()->returnValue();
      }
      break;
    case INTERP:
      return interpFrame()->returnValue();
  }
  MOZ_CRASH("Unexpected state");
}

void FrameIter::setReturnValue(const Value& v) {
  switch (data_.state_) {
    case DONE:
      break;
    case JIT:
      if (jsJitFrame().isBaselineJS()) {
        jsJitFrame().baselineFrame()->setReturnValue(v);
        return;
      }
      break;
    case INTERP:
      interpFrame()->setReturnValue(v);
      return;
  }
  MOZ_CRASH("Unexpected state");
}

size_t FrameIter::numFrameSlots() const {
  switch (data_.state_) {
    case DONE:
      break;
    case JIT: {
      if (isIonScripted()) {
        return ionInlineFrames_.snapshotIterator().numAllocations() -
               ionInlineFrames_.script()->nfixed();
      }
      uint32_t numValueSlots = jsJitFrame().baselineFrameNumValueSlots();
      return numValueSlots - jsJitFrame().script()->nfixed();
    }
    case INTERP:
      MOZ_ASSERT(data_.interpFrames_.sp() >= interpFrame()->base());
      return data_.interpFrames_.sp() - interpFrame()->base();
  }
  MOZ_CRASH("Unexpected state");
}

Value FrameIter::frameSlotValue(size_t index) const {
  switch (data_.state_) {
    case DONE:
      break;
    case JIT:
      if (isIonScripted()) {
        jit::SnapshotIterator si(ionInlineFrames_.snapshotIterator());
        index += ionInlineFrames_.script()->nfixed();
        return si.maybeReadAllocByIndex(index);
      }
      index += jsJitFrame().script()->nfixed();
      return *jsJitFrame().baselineFrame()->valueSlot(index);
    case INTERP:
      return interpFrame()->base()[index];
  }
  MOZ_CRASH("Unexpected state");
}

#ifdef DEBUG
bool js::SelfHostedFramesVisible() {
  static bool checked = false;
  static bool visible = false;
  if (!checked) {
    checked = true;
    char* env = getenv("MOZ_SHOW_ALL_JS_FRAMES");
    visible = !!env;
  }
  return visible;
}
#endif

void NonBuiltinFrameIter::settle() {
  if (!SelfHostedFramesVisible()) {
    while (!done() && hasScript() && script()->selfHosted()) {
      FrameIter::operator++();
    }
  }
}

void NonBuiltinScriptFrameIter::settle() {
  if (!SelfHostedFramesVisible()) {
    while (!done() && script()->selfHosted()) {
      ScriptFrameIter::operator++();
    }
  }
}

bool FrameIter::inPrologue() const {
  if (pc() < script()->main()) {
    return true;
  }
  // If we do a VM call before pushing locals in baseline, the stack frame will
  // not include space for those locals.
  if (pc() == script()->code() && isBaseline() &&
      jsJitFrame().baselineFrameNumValueSlots() < script()->nfixed()) {
    return true;
  }

  return false;
}
