// Copyright 2015 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "src/debug/debug-scopes.h"

#include <memory>

#include "src/ast/ast.h"
#include "src/ast/scopes.h"
#include "src/debug/debug.h"
#include "src/frames-inl.h"
#include "src/globals.h"
#include "src/isolate-inl.h"
#include "src/objects/js-generator-inl.h"
#include "src/objects/module.h"
#include "src/parsing/parse-info.h"
#include "src/parsing/parsing.h"
#include "src/parsing/rewriter.h"

namespace v8 {
namespace internal {

ScopeIterator::ScopeIterator(Isolate* isolate, FrameInspector* frame_inspector,
                             ScopeIterator::Option option)
    : isolate_(isolate),
      frame_inspector_(frame_inspector),
      function_(frame_inspector_->GetFunction()),
      script_(frame_inspector_->GetScript()) {
  if (!frame_inspector->GetContext()->IsContext()) {
    // Optimized frame, context or function cannot be materialized. Give up.
    return;
  }
  context_ = Handle<Context>::cast(frame_inspector->GetContext());

  // We should not instantiate a ScopeIterator for wasm frames.
  DCHECK_NE(Script::TYPE_WASM, frame_inspector->GetScript()->type());

  TryParseAndRetrieveScopes(option);
}

ScopeIterator::~ScopeIterator() { delete info_; }

Handle<Object> ScopeIterator::GetFunctionDebugName() const {
  if (!function_.is_null()) return JSFunction::GetDebugName(function_);

  if (!context_->IsNativeContext()) {
    DisallowHeapAllocation no_gc;
    ScopeInfo* closure_info = context_->closure_context()->scope_info();
    Handle<String> debug_name(closure_info->FunctionDebugName(), isolate_);
    if (debug_name->length() > 0) return debug_name;
  }
  return isolate_->factory()->undefined_value();
}

ScopeIterator::ScopeIterator(Isolate* isolate, Handle<JSFunction> function)
    : isolate_(isolate), context_(function->context(), isolate) {
  if (!function->shared()->IsSubjectToDebugging()) {
    context_ = Handle<Context>();
    return;
  }
  script_ = handle(Script::cast(function->shared()->script()), isolate);
  UnwrapEvaluationContext();
}

ScopeIterator::ScopeIterator(Isolate* isolate,
                             Handle<JSGeneratorObject> generator)
    : isolate_(isolate),
      generator_(generator),
      function_(generator->function(), isolate),
      context_(generator->context(), isolate),
      script_(Script::cast(function_->shared()->script()), isolate) {
  CHECK(function_->shared()->IsSubjectToDebugging());
  TryParseAndRetrieveScopes(DEFAULT);
}

void ScopeIterator::Restart() {
  DCHECK_NOT_NULL(frame_inspector_);
  function_ = frame_inspector_->GetFunction();
  context_ = Handle<Context>::cast(frame_inspector_->GetContext());
  current_scope_ = start_scope_;
  DCHECK_NOT_NULL(current_scope_);
  UnwrapEvaluationContext();
}

void ScopeIterator::TryParseAndRetrieveScopes(ScopeIterator::Option option) {
  // Catch the case when the debugger stops in an internal function.
  Handle<SharedFunctionInfo> shared_info(function_->shared(), isolate_);
  Handle<ScopeInfo> scope_info(shared_info->scope_info(), isolate_);
  if (shared_info->script()->IsUndefined(isolate_)) {
    current_scope_ = closure_scope_ = nullptr;
    context_ = handle(function_->context(), isolate_);
    function_ = Handle<JSFunction>();
    return;
  }

  // Class fields initializer functions don't have any scope
  // information. We short circuit the parsing of the class literal
  // and return an empty context here.
  if (IsClassMembersInitializerFunction(shared_info->kind())) {
    current_scope_ = closure_scope_ = nullptr;
    context_ = Handle<Context>();
    function_ = Handle<JSFunction>();
    return;
  }

  DCHECK_NE(IGNORE_NESTED_SCOPES, option);
  bool ignore_nested_scopes = false;
  if (shared_info->HasBreakInfo() && frame_inspector_ != nullptr) {
    // The source position at return is always the end of the function,
    // which is not consistent with the current scope chain. Therefore all
    // nested with, catch and block contexts are skipped, and we can only
    // inspect the function scope.
    // This can only happen if we set a break point inside right before the
    // return, which requires a debug info to be available.
    Handle<DebugInfo> debug_info(shared_info->GetDebugInfo(), isolate_);

    // Find the break point where execution has stopped.
    BreakLocation location = BreakLocation::FromFrame(debug_info, GetFrame());

    ignore_nested_scopes = location.IsReturn();
  }

  // Reparse the code and analyze the scopes.
  // Check whether we are in global, eval or function code.
  if (scope_info->scope_type() == FUNCTION_SCOPE) {
    // Inner function.
    info_ = new ParseInfo(isolate_, shared_info);
  } else {
    // Global or eval code.
    Handle<Script> script(Script::cast(shared_info->script()), isolate_);
    info_ = new ParseInfo(isolate_, script);
    if (scope_info->scope_type() == EVAL_SCOPE) {
      info_->set_eval();
      if (!context_->IsNativeContext()) {
        info_->set_outer_scope_info(handle(context_->scope_info(), isolate_));
      }
      // Language mode may be inherited from the eval caller.
      // Retrieve it from shared function info.
      info_->set_language_mode(shared_info->language_mode());
    } else if (scope_info->scope_type() == MODULE_SCOPE) {
      DCHECK(info_->is_module());
    } else {
      DCHECK_EQ(SCRIPT_SCOPE, scope_info->scope_type());
    }
  }

  if (parsing::ParseAny(info_, shared_info, isolate_) &&
      Rewriter::Rewrite(info_)) {
    info_->ast_value_factory()->Internalize(isolate_);
    closure_scope_ = info_->literal()->scope();

    if (option == COLLECT_NON_LOCALS) {
      DCHECK(non_locals_.is_null());
      non_locals_ = info_->literal()->scope()->CollectNonLocals(
          isolate_, info_, StringSet::New(isolate_));
    }

    CHECK(DeclarationScope::Analyze(info_));
    if (ignore_nested_scopes) {
      current_scope_ = closure_scope_;
      start_scope_ = current_scope_;
      if (closure_scope_->NeedsContext()) {
        context_ = handle(context_->closure_context(), isolate_);
      }
    } else {
      RetrieveScopeChain(closure_scope_);
    }
    UnwrapEvaluationContext();
  } else {
    // A failed reparse indicates that the preparser has diverged from the
    // parser or that the preparse data given to the initial parse has been
    // faulty. We fail in debug mode but in release mode we only provide the
    // information we get from the context chain but nothing about
    // completely stack allocated scopes or stack allocated locals.
    // Or it could be due to stack overflow.
    // Silently fail by presenting an empty context chain.
    CHECK(isolate_->has_pending_exception());
    isolate_->clear_pending_exception();
    context_ = Handle<Context>();
  }
}

void ScopeIterator::UnwrapEvaluationContext() {
  if (!context_->IsDebugEvaluateContext()) return;
  Context* current = *context_;
  do {
    Object* wrapped = current->get(Context::WRAPPED_CONTEXT_INDEX);
    if (wrapped->IsContext()) {
      current = Context::cast(wrapped);
    } else {
      DCHECK_NOT_NULL(current->previous());
      current = current->previous();
    }
  } while (current->IsDebugEvaluateContext());
  context_ = handle(current, isolate_);
}

Handle<JSObject> ScopeIterator::MaterializeScopeDetails() {
  // Calculate the size of the result.
  Handle<FixedArray> details =
      isolate_->factory()->NewFixedArray(kScopeDetailsSize);
  // Fill in scope details.
  details->set(kScopeDetailsTypeIndex, Smi::FromInt(Type()));
  Handle<JSObject> scope_object = ScopeObject(Mode::ALL);
  details->set(kScopeDetailsObjectIndex, *scope_object);
  if (Type() == ScopeTypeGlobal || Type() == ScopeTypeScript) {
    return isolate_->factory()->NewJSArrayWithElements(details);
  } else if (HasContext()) {
    Handle<Object> closure_name = GetFunctionDebugName();
    details->set(kScopeDetailsNameIndex, *closure_name);
    details->set(kScopeDetailsStartPositionIndex,
                 Smi::FromInt(start_position()));
    details->set(kScopeDetailsEndPositionIndex, Smi::FromInt(end_position()));
    if (InInnerScope()) {
      details->set(kScopeDetailsFunctionIndex, *function_);
    }
  }
  return isolate_->factory()->NewJSArrayWithElements(details);
}

bool ScopeIterator::HasPositionInfo() {
  return InInnerScope() || !context_->IsNativeContext();
}

int ScopeIterator::start_position() {
  if (InInnerScope()) return current_scope_->start_position();
  if (context_->IsNativeContext()) return 0;
  return context_->closure_context()->scope_info()->StartPosition();
}

int ScopeIterator::end_position() {
  if (InInnerScope()) return current_scope_->end_position();
  if (context_->IsNativeContext()) return 0;
  return context_->closure_context()->scope_info()->EndPosition();
}

bool ScopeIterator::DeclaresLocals(Mode mode) const {
  ScopeType type = Type();

  if (type == ScopeTypeWith) return mode == Mode::ALL;
  if (type == ScopeTypeGlobal) return mode == Mode::ALL;

  bool declares_local = false;
  auto visitor = [&](Handle<String> name, Handle<Object> value) {
    declares_local = true;
    return true;
  };
  VisitScope(visitor, mode);
  return declares_local;
}

bool ScopeIterator::HasContext() const {
  return !InInnerScope() || current_scope_->NeedsContext();
}

void ScopeIterator::Next() {
  DCHECK(!Done());

  ScopeType scope_type = Type();

  if (scope_type == ScopeTypeGlobal) {
    // The global scope is always the last in the chain.
    DCHECK(context_->IsNativeContext());
    context_ = Handle<Context>();
    DCHECK(Done());
    return;
  }

  bool inner = InInnerScope();
  if (current_scope_ == closure_scope_) function_ = Handle<JSFunction>();

  if (scope_type == ScopeTypeScript) {
    DCHECK_IMPLIES(InInnerScope(), current_scope_->is_script_scope());
    seen_script_scope_ = true;
    if (context_->IsScriptContext()) {
      context_ = handle(context_->previous(), isolate_);
    }
  } else if (!inner) {
    DCHECK(!context_->IsNativeContext());
    context_ = handle(context_->previous(), isolate_);
  } else {
    DCHECK_NOT_NULL(current_scope_);
    do {
      if (current_scope_->NeedsContext()) {
        DCHECK_NOT_NULL(context_->previous());
        context_ = handle(context_->previous(), isolate_);
      }
      DCHECK_IMPLIES(InInnerScope(), current_scope_->outer_scope() != nullptr);
      current_scope_ = current_scope_->outer_scope();
      // Repeat to skip hidden scopes.
    } while (current_scope_->is_hidden());
  }

  UnwrapEvaluationContext();
}


// Return the type of the current scope.
ScopeIterator::ScopeType ScopeIterator::Type() const {
  DCHECK(!Done());
  if (InInnerScope()) {
    switch (current_scope_->scope_type()) {
      case FUNCTION_SCOPE:
        DCHECK_IMPLIES(current_scope_->NeedsContext(),
                       context_->IsFunctionContext());
        return ScopeTypeLocal;
      case MODULE_SCOPE:
        DCHECK_IMPLIES(current_scope_->NeedsContext(),
                       context_->IsModuleContext());
        return ScopeTypeModule;
      case SCRIPT_SCOPE:
        DCHECK_IMPLIES(
            current_scope_->NeedsContext(),
            context_->IsScriptContext() || context_->IsNativeContext());
        return ScopeTypeScript;
      case WITH_SCOPE:
        DCHECK_IMPLIES(
            current_scope_->NeedsContext(),
            context_->IsWithContext() || context_->IsDebugEvaluateContext());
        return ScopeTypeWith;
      case CATCH_SCOPE:
        DCHECK(context_->IsCatchContext());
        return ScopeTypeCatch;
      case BLOCK_SCOPE:
        DCHECK_IMPLIES(current_scope_->NeedsContext(),
                       context_->IsBlockContext());
        return ScopeTypeBlock;
      case EVAL_SCOPE:
        DCHECK_IMPLIES(current_scope_->NeedsContext(),
                       context_->IsEvalContext());
        return ScopeTypeEval;
    }
    UNREACHABLE();
  }
  if (context_->IsNativeContext()) {
    DCHECK(context_->global_object()->IsJSGlobalObject());
    // If we are at the native context and have not yet seen script scope,
    // fake it.
    return seen_script_scope_ ? ScopeTypeGlobal : ScopeTypeScript;
  }
  if (context_->IsFunctionContext() || context_->IsEvalContext()) {
    return ScopeTypeClosure;
  }
  if (context_->IsCatchContext()) {
    return ScopeTypeCatch;
  }
  if (context_->IsBlockContext()) {
    return ScopeTypeBlock;
  }
  if (context_->IsModuleContext()) {
    return ScopeTypeModule;
  }
  if (context_->IsScriptContext()) {
    return ScopeTypeScript;
  }
  DCHECK(context_->IsWithContext() || context_->IsDebugEvaluateContext());
  return ScopeTypeWith;
}

Handle<JSObject> ScopeIterator::ScopeObject(Mode mode) {
  DCHECK(!Done());

  ScopeType type = Type();
  if (type == ScopeTypeGlobal) {
    DCHECK_EQ(Mode::ALL, mode);
    return handle(context_->global_proxy(), isolate_);
  }
  if (type == ScopeTypeWith) {
    DCHECK_EQ(Mode::ALL, mode);
    return WithContextExtension();
  }

  Handle<JSObject> scope = isolate_->factory()->NewJSObjectWithNullProto();
  auto visitor = [=](Handle<String> name, Handle<Object> value) {
    JSObject::AddProperty(isolate_, scope, name, value, NONE);
    return false;
  };

  VisitScope(visitor, mode);
  return scope;
}

void ScopeIterator::VisitScope(const Visitor& visitor, Mode mode) const {
  switch (Type()) {
    case ScopeTypeLocal:
    case ScopeTypeClosure:
    case ScopeTypeCatch:
    case ScopeTypeBlock:
    case ScopeTypeEval:
      return VisitLocalScope(visitor, mode);
    case ScopeTypeModule:
      if (InInnerScope()) {
        return VisitLocalScope(visitor, mode);
      }
      DCHECK_EQ(Mode::ALL, mode);
      return VisitModuleScope(visitor);
    case ScopeTypeScript:
      DCHECK_EQ(Mode::ALL, mode);
      return VisitScriptScope(visitor);
    case ScopeTypeWith:
    case ScopeTypeGlobal:
      UNREACHABLE();
  }
}

bool ScopeIterator::SetVariableValue(Handle<String> name,
                                     Handle<Object> value) {
  DCHECK(!Done());
  name = isolate_->factory()->InternalizeString(name);
  switch (Type()) {
    case ScopeTypeGlobal:
    case ScopeTypeWith:
      break;

    case ScopeTypeEval:
    case ScopeTypeBlock:
    case ScopeTypeCatch:
    case ScopeTypeModule:
      if (InInnerScope()) return SetLocalVariableValue(name, value);
      if (Type() == ScopeTypeModule && SetModuleVariableValue(name, value)) {
        return true;
      }
      return SetContextVariableValue(name, value);

    case ScopeTypeLocal:
    case ScopeTypeClosure:
      if (InInnerScope()) {
        DCHECK_EQ(ScopeTypeLocal, Type());
        if (SetLocalVariableValue(name, value)) return true;
        // There may not be an associated context since we're InInnerScope().
        if (!current_scope_->NeedsContext()) return false;
      } else {
        DCHECK_EQ(ScopeTypeClosure, Type());
        if (SetContextVariableValue(name, value)) return true;
      }
      // The above functions only set variables statically declared in the
      // function. There may be eval-introduced variables. Check them in
      // SetContextExtensionValue.
      return SetContextExtensionValue(name, value);

    case ScopeTypeScript:
      return SetScriptVariableValue(name, value);
  }
  return false;
}

Handle<StringSet> ScopeIterator::GetNonLocals() { return non_locals_; }

#ifdef DEBUG
// Debug print of the content of the current scope.
void ScopeIterator::DebugPrint() {
  StdoutStream os;
  DCHECK(!Done());
  switch (Type()) {
    case ScopeIterator::ScopeTypeGlobal:
      os << "Global:\n";
      context_->Print(os);
      break;

    case ScopeIterator::ScopeTypeLocal: {
      os << "Local:\n";
      if (current_scope_->NeedsContext()) {
        context_->Print(os);
        if (context_->has_extension()) {
          Handle<HeapObject> extension(context_->extension(), isolate_);
          DCHECK(extension->IsJSContextExtensionObject());
          extension->Print(os);
        }
      }
      break;
    }

    case ScopeIterator::ScopeTypeWith:
      os << "With:\n";
      context_->extension()->Print(os);
      break;

    case ScopeIterator::ScopeTypeCatch:
      os << "Catch:\n";
      context_->extension()->Print(os);
      context_->get(Context::THROWN_OBJECT_INDEX)->Print(os);
      break;

    case ScopeIterator::ScopeTypeClosure:
      os << "Closure:\n";
      context_->Print(os);
      if (context_->has_extension()) {
        Handle<HeapObject> extension(context_->extension(), isolate_);
        DCHECK(extension->IsJSContextExtensionObject());
        extension->Print(os);
      }
      break;

    case ScopeIterator::ScopeTypeScript:
      os << "Script:\n";
      context_->global_object()
          ->native_context()
          ->script_context_table()
          ->Print(os);
      break;

    default:
      UNREACHABLE();
  }
  PrintF("\n");
}
#endif

int ScopeIterator::GetSourcePosition() {
  if (frame_inspector_) {
    return frame_inspector_->GetSourcePosition();
  } else {
    DCHECK(!generator_.is_null());
    return generator_->source_position();
  }
}

void ScopeIterator::RetrieveScopeChain(DeclarationScope* scope) {
  DCHECK_NOT_NULL(scope);

  const int position = GetSourcePosition();

  Scope* parent = nullptr;
  Scope* current = scope;
  while (parent != current) {
    parent = current;
    for (Scope* inner_scope = current->inner_scope(); inner_scope != nullptr;
         inner_scope = inner_scope->sibling()) {
      int beg_pos = inner_scope->start_position();
      int end_pos = inner_scope->end_position();
      DCHECK((beg_pos >= 0 && end_pos >= 0) || inner_scope->is_hidden());
      if (beg_pos <= position && position < end_pos) {
        // Don't walk into inner functions.
        if (!inner_scope->is_function_scope()) {
          current = inner_scope;
        }
        break;
      }
    }
  }

  start_scope_ = current;
  current_scope_ = current;
}

void ScopeIterator::VisitScriptScope(const Visitor& visitor) const {
  Handle<JSGlobalObject> global(context_->global_object(), isolate_);
  Handle<ScriptContextTable> script_contexts(
      global->native_context()->script_context_table(), isolate_);

  // Skip the first script since that just declares 'this'.
  for (int context_index = 1; context_index < script_contexts->used();
       context_index++) {
    Handle<Context> context = ScriptContextTable::GetContext(
        isolate_, script_contexts, context_index);
    Handle<ScopeInfo> scope_info(context->scope_info(), isolate_);
    if (VisitContextLocals(visitor, scope_info, context)) return;
  }
}

void ScopeIterator::VisitModuleScope(const Visitor& visitor) const {
  DCHECK(context_->IsModuleContext());

  Handle<ScopeInfo> scope_info(context_->scope_info(), isolate_);
  if (VisitContextLocals(visitor, scope_info, context_)) return;

  int count_index = scope_info->ModuleVariableCountIndex();
  int module_variable_count = Smi::cast(scope_info->get(count_index))->value();

  Handle<Module> module(context_->module(), isolate_);

  for (int i = 0; i < module_variable_count; ++i) {
    int index;
    Handle<String> name;
    {
      String* raw_name;
      scope_info->ModuleVariable(i, &raw_name, &index);
      CHECK(!ScopeInfo::VariableIsSynthetic(raw_name));
      name = handle(raw_name, isolate_);
    }
    Handle<Object> value = Module::LoadVariable(isolate_, module, index);

    // Reflect variables under TDZ as undeclared in scope object.
    if (value->IsTheHole(isolate_)) continue;
    if (visitor(name, value)) return;
  }
}

bool ScopeIterator::VisitContextLocals(const Visitor& visitor,
                                       Handle<ScopeInfo> scope_info,
                                       Handle<Context> context) const {
  // Fill all context locals to the context extension.
  for (int i = 0; i < scope_info->ContextLocalCount(); ++i) {
    Handle<String> name(scope_info->ContextLocalName(i), isolate_);
    if (ScopeInfo::VariableIsSynthetic(*name)) continue;
    int context_index = Context::MIN_CONTEXT_SLOTS + i;
    Handle<Object> value(context->get(context_index), isolate_);
    // Reflect variables under TDZ as undefined in scope object.
    if (value->IsTheHole(isolate_)) continue;
    if (visitor(name, value)) return true;
  }
  return false;
}

bool ScopeIterator::VisitLocals(const Visitor& visitor, Mode mode) const {
  for (Variable* var : *current_scope_->locals()) {
    if (!var->is_this() && ScopeInfo::VariableIsSynthetic(*var->name())) {
      continue;
    }

    int index = var->index();
    Handle<Object> value;
    switch (var->location()) {
      case VariableLocation::LOOKUP:
        UNREACHABLE();
        break;

      case VariableLocation::UNALLOCATED:
        if (!var->is_this()) continue;
        // No idea why we only add it sometimes.
        if (mode == Mode::ALL) continue;
        // No idea why this diverges...
        value = frame_inspector_->GetReceiver();
        break;

      case VariableLocation::PARAMETER: {
        if (frame_inspector_ == nullptr) {
          // Get the variable from the suspended generator.
          DCHECK(!generator_.is_null());
          if (var->is_this()) {
            value = handle(generator_->receiver(), isolate_);
          } else {
            FixedArray* parameters_and_registers =
                generator_->parameters_and_registers();
            DCHECK_LT(index, parameters_and_registers->length());
            value = handle(parameters_and_registers->get(index), isolate_);
          }
        } else {
          value = var->is_this() ? frame_inspector_->GetReceiver()
                                 : frame_inspector_->GetParameter(index);

          if (value->IsOptimizedOut(isolate_)) {
            value = isolate_->factory()->undefined_value();
          } else if (var->is_this() && value->IsTheHole(isolate_)) {
            value = isolate_->factory()->undefined_value();
          }
        }
        break;
      }

      case VariableLocation::LOCAL:
        if (frame_inspector_ == nullptr) {
          // Get the variable from the suspended generator.
          DCHECK(!generator_.is_null());
          FixedArray* parameters_and_registers =
              generator_->parameters_and_registers();
          int parameter_count =
              function_->shared()->scope_info()->ParameterCount();
          index += parameter_count;
          DCHECK_LT(index, parameters_and_registers->length());
          value = handle(parameters_and_registers->get(index), isolate_);
          if (value->IsTheHole(isolate_)) {
            value = isolate_->factory()->undefined_value();
          }
        } else {
          value = frame_inspector_->GetExpression(index);
          if (value->IsOptimizedOut(isolate_)) {
            // We'll rematerialize this later.
            if (current_scope_->is_declaration_scope() &&
                current_scope_->AsDeclarationScope()->arguments() == var) {
              continue;
            }
            value = isolate_->factory()->undefined_value();
          } else if (value->IsTheHole(isolate_)) {
            // Reflect variables under TDZ as undeclared in scope object.
            continue;
          }
        }
        break;

      case VariableLocation::CONTEXT:
        if (mode == Mode::STACK) continue;
        // TODO(verwaest): Why don't we want to show it if it's there?...
        if (var->is_this()) continue;
        DCHECK(var->IsContextSlot());
        value = handle(context_->get(index), isolate_);
        // Reflect variables under TDZ as undeclared in scope object.
        if (value->IsTheHole(isolate_)) continue;
        break;

      case VariableLocation::MODULE: {
        if (mode == Mode::STACK) continue;
        // if (var->IsExport()) continue;
        Handle<Module> module(context_->module(), isolate_);
        value = Module::LoadVariable(isolate_, module, var->index());
        // Reflect variables under TDZ as undeclared in scope object.
        if (value->IsTheHole(isolate_)) continue;
        break;
      }
    }

    if (visitor(var->name(), value)) return true;
  }
  return false;
}

// Retrieve the with-context extension object. If the extension object is
// a proxy, return an empty object.
Handle<JSObject> ScopeIterator::WithContextExtension() {
  DCHECK(context_->IsWithContext());
  if (context_->extension_receiver()->IsJSProxy()) {
    return isolate_->factory()->NewJSObjectWithNullProto();
  }
  return handle(JSObject::cast(context_->extension_receiver()), isolate_);
}

// Create a plain JSObject which materializes the block scope for the specified
// block context.
void ScopeIterator::VisitLocalScope(const Visitor& visitor, Mode mode) const {
  if (InInnerScope()) {
    if (VisitLocals(visitor, mode)) return;
    if (mode == Mode::STACK && Type() == ScopeTypeLocal) {
      // Hide |this| in arrow functions that may be embedded in other functions
      // but don't force |this| to be context-allocated. Otherwise we'd find the
      // wrong |this| value.
      if (!closure_scope_->has_this_declaration() &&
          !non_locals_->Has(isolate_, isolate_->factory()->this_string())) {
        if (visitor(isolate_->factory()->this_string(),
                    isolate_->factory()->undefined_value()))
          return;
      }
      // Add |arguments| to the function scope even if it wasn't used.
      // Currently we don't yet support materializing the arguments object of
      // suspended generators. We'd need to read the arguments out from the
      // suspended generator rather than from an activation as
      // FunctionGetArguments does.
      if (frame_inspector_ != nullptr && !closure_scope_->is_arrow_scope() &&
          (closure_scope_->arguments() == nullptr ||
           frame_inspector_->GetExpression(closure_scope_->arguments()->index())
               ->IsOptimizedOut(isolate_))) {
        JavaScriptFrame* frame = GetFrame();
        Handle<JSObject> arguments = Accessors::FunctionGetArguments(
            frame, frame_inspector_->inlined_frame_index());
        if (visitor(isolate_->factory()->arguments_string(), arguments)) return;
      }
    }
  } else {
    DCHECK_EQ(Mode::ALL, mode);
    Handle<ScopeInfo> scope_info(context_->scope_info(), isolate_);
    if (VisitContextLocals(visitor, scope_info, context_)) return;
  }

  if (mode == Mode::ALL && HasContext()) {
    DCHECK(!context_->IsScriptContext());
    DCHECK(!context_->IsNativeContext());
    DCHECK(!context_->IsWithContext());
    if (!context_->scope_info()->CallsSloppyEval()) return;
    if (context_->extension_object() == nullptr) return;
    Handle<JSObject> extension(context_->extension_object(), isolate_);
    Handle<FixedArray> keys =
        KeyAccumulator::GetKeys(extension, KeyCollectionMode::kOwnOnly,
                                ENUMERABLE_STRINGS)
            .ToHandleChecked();

    for (int i = 0; i < keys->length(); i++) {
      // Names of variables introduced by eval are strings.
      DCHECK(keys->get(i)->IsString());
      Handle<String> key(String::cast(keys->get(i)), isolate_);
      Handle<Object> value = JSReceiver::GetDataProperty(extension, key);
      if (visitor(key, value)) return;
    }
  }
}

bool ScopeIterator::SetLocalVariableValue(Handle<String> variable_name,
                                          Handle<Object> new_value) {
  // TODO(verwaest): Walk parameters backwards, not forwards.
  // TODO(verwaest): Use VariableMap rather than locals() list for lookup.
  for (Variable* var : *current_scope_->locals()) {
    if (String::Equals(isolate_, var->name(), variable_name)) {
      int index = var->index();
      switch (var->location()) {
        case VariableLocation::LOOKUP:
        case VariableLocation::UNALLOCATED:
          // Drop assignments to unallocated locals.
          DCHECK(var->is_this() ||
                 *variable_name == ReadOnlyRoots(isolate_).arguments_string());
          return false;

        case VariableLocation::PARAMETER: {
          if (var->is_this()) return false;
          if (frame_inspector_ == nullptr) {
            // Set the variable in the suspended generator.
            DCHECK(!generator_.is_null());
            Handle<FixedArray> parameters_and_registers(
                generator_->parameters_and_registers(), isolate_);
            DCHECK_LT(index, parameters_and_registers->length());
            parameters_and_registers->set(index, *new_value);
          } else {
            JavaScriptFrame* frame = GetFrame();
            if (frame->is_optimized()) return false;

            frame->SetParameterValue(index, *new_value);
          }
          return true;
        }

        case VariableLocation::LOCAL:
          if (frame_inspector_ == nullptr) {
            // Set the variable in the suspended generator.
            DCHECK(!generator_.is_null());
            int parameter_count =
                function_->shared()->scope_info()->ParameterCount();
            index += parameter_count;
            Handle<FixedArray> parameters_and_registers(
                generator_->parameters_and_registers(), isolate_);
            DCHECK_LT(index, parameters_and_registers->length());
            parameters_and_registers->set(index, *new_value);
          } else {
            // Set the variable on the stack.
            JavaScriptFrame* frame = GetFrame();
            if (frame->is_optimized()) return false;

            frame->SetExpression(index, *new_value);
          }
          return true;

        case VariableLocation::CONTEXT:
          DCHECK(var->IsContextSlot());
          context_->set(index, *new_value);
          return true;

        case VariableLocation::MODULE:
          if (!var->IsExport()) return false;
          Handle<Module> module(context_->module(), isolate_);
          Module::StoreVariable(module, var->index(), new_value);
          return true;
      }
      UNREACHABLE();
    }
  }

  return false;
}

bool ScopeIterator::SetContextExtensionValue(Handle<String> variable_name,
                                             Handle<Object> new_value) {
  if (!context_->has_extension()) return false;

  DCHECK(context_->extension_object()->IsJSContextExtensionObject());
  Handle<JSObject> ext(context_->extension_object(), isolate_);
  LookupIterator it(isolate_, ext, variable_name, LookupIterator::OWN);
  Maybe<bool> maybe = JSReceiver::HasOwnProperty(ext, variable_name);
  DCHECK(maybe.IsJust());
  if (!maybe.FromJust()) return false;

  CHECK(Object::SetDataProperty(&it, new_value).ToChecked());
  return true;
}

bool ScopeIterator::SetContextVariableValue(Handle<String> variable_name,
                                            Handle<Object> new_value) {
  Handle<ScopeInfo> scope_info(context_->scope_info(), isolate_);

  VariableMode mode;
  InitializationFlag flag;
  MaybeAssignedFlag maybe_assigned_flag;
  int slot_index = ScopeInfo::ContextSlotIndex(scope_info, variable_name, &mode,
                                               &flag, &maybe_assigned_flag);
  if (slot_index < 0) return false;

  context_->set(slot_index, *new_value);
  return true;
}

bool ScopeIterator::SetModuleVariableValue(Handle<String> variable_name,
                                           Handle<Object> new_value) {
  int cell_index;
  VariableMode mode;
  InitializationFlag init_flag;
  MaybeAssignedFlag maybe_assigned_flag;
  cell_index = context_->scope_info()->ModuleIndex(
      variable_name, &mode, &init_flag, &maybe_assigned_flag);

  // Setting imports is currently not supported.
  if (ModuleDescriptor::GetCellIndexKind(cell_index) !=
      ModuleDescriptor::kExport) {
    return false;
  }

  Handle<Module> module(context_->module(), isolate_);
  Module::StoreVariable(module, cell_index, new_value);
  return true;
}

bool ScopeIterator::SetScriptVariableValue(Handle<String> variable_name,
                                           Handle<Object> new_value) {
  Handle<ScriptContextTable> script_contexts(
      context_->global_object()->native_context()->script_context_table(),
      isolate_);
  ScriptContextTable::LookupResult lookup_result;
  if (ScriptContextTable::Lookup(isolate_, script_contexts, variable_name,
                                 &lookup_result)) {
    Handle<Context> script_context = ScriptContextTable::GetContext(
        isolate_, script_contexts, lookup_result.context_index);
    script_context->set(lookup_result.slot_index, *new_value);
    return true;
  }

  return false;
}

}  // namespace internal
}  // namespace v8