// 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