blob: 3142240aad00699a9061a7193aa4585df1fd24fd [file] [log] [blame]
// 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/common/globals.h"
#include "src/debug/debug.h"
#include "src/execution/frames-inl.h"
#include "src/execution/isolate-inl.h"
#include "src/objects/js-generator-inl.h"
#include "src/objects/source-text-module.h"
#include "src/objects/string-set-inl.h"
#include "src/parsing/parse-info.h"
#include "src/parsing/parsing.h"
#include "src/parsing/rewriter.h"
#include "src/utils/ostreams.h"
namespace v8 {
namespace internal {
ScopeIterator::ScopeIterator(Isolate* isolate, FrameInspector* frame_inspector,
ReparseStrategy strategy)
: 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(strategy);
}
ScopeIterator::~ScopeIterator() = default;
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(ReparseStrategy::kFunctionLiteral);
}
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();
}
namespace {
// Takes the scope of a parsed script, a function and a break location
// inside the function. The result is the innermost lexical scope around
// the break point, which serves as the starting point of the ScopeIterator.
// And the scope of the function that was passed in (called closure scope).
//
// The start scope is guaranteed to be either the closure scope itself,
// or a child of the closure scope.
class ScopeChainRetriever {
public:
ScopeChainRetriever(DeclarationScope* scope, Handle<JSFunction> function,
int position)
: scope_(scope),
break_scope_start_(function->shared().StartPosition()),
break_scope_end_(function->shared().EndPosition()),
is_default_constructor_(
IsDefaultConstructor(function->shared().kind())),
position_(position) {
DCHECK_NOT_NULL(scope);
RetrieveScopes();
}
DeclarationScope* ClosureScope() { return closure_scope_; }
Scope* StartScope() { return start_scope_; }
private:
DeclarationScope* scope_;
const int break_scope_start_;
const int break_scope_end_;
const bool is_default_constructor_;
const int position_;
DeclarationScope* closure_scope_ = nullptr;
Scope* start_scope_ = nullptr;
void RetrieveScopes() {
if (is_default_constructor_) {
// Even though the DefaultBaseConstructor is a child of a Class scope, the
// source positions are *not* nested. This means the actual scope for the
// DefaultBaseConstructor needs to be found by doing a DFS.
RetrieveScopeChainDefaultConstructor(scope_);
} else {
RetrieveScopeChain();
}
DCHECK_NOT_NULL(closure_scope_);
DCHECK_NOT_NULL(start_scope_);
}
bool RetrieveScopeChainDefaultConstructor(Scope* scope) {
const int beg_pos = scope->start_position();
const int end_pos = scope->end_position();
if (beg_pos == position_ && end_pos == position_) {
DCHECK(scope->is_function_scope());
DCHECK(
IsDefaultConstructor(scope->AsDeclarationScope()->function_kind()));
start_scope_ = scope;
closure_scope_ = scope->AsDeclarationScope();
return true;
}
for (Scope* inner_scope = scope->inner_scope(); inner_scope != nullptr;
inner_scope = inner_scope->sibling()) {
if (RetrieveScopeChainDefaultConstructor(inner_scope)) return true;
}
return false;
}
void RetrieveScopeChain() {
Scope* parent = nullptr;
Scope* current = scope_;
SetClosureScopeIfFound(current);
while (parent != current) {
parent = current;
for (Scope* inner_scope = current->inner_scope(); inner_scope != nullptr;
inner_scope = inner_scope->sibling()) {
if (SetClosureScopeIfFound(inner_scope) ||
ContainsPosition(inner_scope)) {
current = inner_scope;
break;
}
}
}
start_scope_ = current;
}
bool SetClosureScopeIfFound(Scope* scope) {
const int start = scope->start_position();
const int end = scope->end_position();
if (start == break_scope_start_ && end == break_scope_end_) {
closure_scope_ = scope->AsDeclarationScope();
return true;
}
return false;
}
bool ContainsPosition(Scope* scope) {
const int start = scope->start_position();
const int end = scope->end_position();
// In case the closure_scope_ hasn't been found yet, we are less strict
// about recursing downwards. This might be the case for nested arrow
// functions that have the same end position.
const bool position_fits_end =
closure_scope_ ? position_ < end : position_ <= end;
return start < position_ && position_fits_end;
}
};
} // namespace
void ScopeIterator::TryParseAndRetrieveScopes(ReparseStrategy strategy) {
// 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;
}
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.
// Depending on the choosen strategy, the whole script or just
// the closure is re-parsed for function scopes.
Handle<Script> script(Script::cast(shared_info->script()), isolate_);
// Pick between flags for a single function compilation, or an eager
// compilation of the whole script.
UnoptimizedCompileFlags flags =
(scope_info->scope_type() == FUNCTION_SCOPE &&
strategy == ReparseStrategy::kFunctionLiteral)
? UnoptimizedCompileFlags::ForFunctionCompile(isolate_, *shared_info)
: UnoptimizedCompileFlags::ForScriptCompile(isolate_, *script)
.set_is_eager(true);
MaybeHandle<ScopeInfo> maybe_outer_scope;
if (scope_info->scope_type() == EVAL_SCOPE || script->is_wrapped()) {
flags.set_is_eval(true);
if (!context_->IsNativeContext()) {
maybe_outer_scope = handle(context_->scope_info(), isolate_);
}
// Language mode may be inherited from the eval caller.
// Retrieve it from shared function info.
flags.set_outer_language_mode(shared_info->language_mode());
} else if (scope_info->scope_type() == MODULE_SCOPE) {
DCHECK(flags.is_module());
} else {
DCHECK(scope_info->scope_type() == SCRIPT_SCOPE ||
scope_info->scope_type() == FUNCTION_SCOPE);
}
UnoptimizedCompileState compile_state(isolate_);
info_ = std::make_unique<ParseInfo>(isolate_, flags, &compile_state);
const bool parse_result =
flags.is_toplevel()
? parsing::ParseProgram(info_.get(), script, maybe_outer_scope,
isolate_, parsing::ReportStatisticsMode::kNo)
: parsing::ParseFunction(info_.get(), shared_info, isolate_,
parsing::ReportStatisticsMode::kNo);
if (parse_result) {
DeclarationScope* literal_scope = info_->literal()->scope();
ScopeChainRetriever scope_chain_retriever(literal_scope, function_,
GetSourcePosition());
start_scope_ = scope_chain_retriever.StartScope();
current_scope_ = start_scope_;
// In case of a FUNCTION_SCOPE, the ScopeIterator expects
// {closure_scope_} to be set to the scope of the function.
closure_scope_ = scope_info->scope_type() == FUNCTION_SCOPE
? scope_chain_retriever.ClosureScope()
: literal_scope;
if (ignore_nested_scopes) {
current_scope_ = closure_scope_;
start_scope_ = current_scope_;
// ignore_nested_scopes is only used for the return-position breakpoint,
// so we can safely assume that the closure context for the current
// function exists if it needs one.
if (closure_scope_->NeedsContext()) {
context_ = handle(context_->closure_context(), isolate_);
}
}
UnwrapEvaluationContext();
} else {
// A failed reparse indicates that the preparser has diverged from the
// parser, that the preparse data given to the initial parse was faulty, or
// a stack overflow.
// TODO(leszeks): This error is pretty unexpected, so we could report the
// error in debug mode. Better to not fail in release though, in case it's
// just a stack overflow.
// Silently fail by presenting an empty context chain.
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(!current.previous().is_null());
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,
ScopeType scope_type) {
declares_local = true;
return true;
};
VisitScope(visitor, mode);
return declares_local;
}
bool ScopeIterator::HasContext() const {
return !InInnerScope() || NeedsAndHasContext();
}
bool ScopeIterator::NeedsAndHasContext() const {
if (!current_scope_->NeedsContext()) return false;
// Generally, if a scope needs a context, then we can assume that it has a
// context. However, the stack check during function entry happens before the
// function has a chance to create and push its own context, so we must check
// for the case where the function is executing in its parent context. This
// case is only possible in function scopes; top-level code (modules and
// non-module scripts) begin execution in the context they need and don't have
// a separate step to push the correct context.
return !(current_scope_ == closure_scope_ &&
current_scope_->is_function_scope() && !function_.is_null() &&
function_->context() == *context_);
}
void ScopeIterator::AdvanceOneScope() {
if (NeedsAndHasContext()) {
DCHECK(!context_->previous().is_null());
context_ = handle(context_->previous(), isolate_);
}
DCHECK(current_scope_->outer_scope() != nullptr);
current_scope_ = current_scope_->outer_scope();
}
void ScopeIterator::AdvanceToNonHiddenScope() {
do {
AdvanceOneScope();
} while (current_scope_->is_hidden());
}
void ScopeIterator::AdvanceContext() {
DCHECK(!context_->IsNativeContext());
context_ = handle(context_->previous(), isolate_);
// While advancing one context, we need to advance at least one
// scope, but until we hit the next scope that actually requires
// a context. All the locals collected along the way build the
// blocklist for debug-evaluate for this context.
locals_ = StringSet::New(isolate_);
do {
if (!current_scope_ || !current_scope_->outer_scope()) break;
current_scope_ = current_scope_->outer_scope();
CollectLocalsFromCurrentScope();
} while (!NeedsAndHasContext());
}
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 leaving_closure = current_scope_ == closure_scope_;
if (scope_type == ScopeTypeScript) {
DCHECK_IMPLIES(InInnerScope() && !leaving_closure,
current_scope_->is_script_scope());
seen_script_scope_ = true;
if (context_->IsScriptContext()) {
context_ = handle(context_->previous(), isolate_);
}
} else if (!InInnerScope()) {
AdvanceContext();
} else {
DCHECK_NOT_NULL(current_scope_);
AdvanceToNonHiddenScope();
if (leaving_closure) {
DCHECK(current_scope_ != closure_scope_);
// Edge case when we just go past {closure_scope_}. This case
// already needs to start collecting locals for the blocklist.
locals_ = StringSet::New(isolate_);
CollectLocalsFromCurrentScope();
}
}
if (leaving_closure) function_ = Handle<JSFunction>();
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(NeedsAndHasContext(),
context_->IsFunctionContext() ||
context_->IsDebugEvaluateContext());
return ScopeTypeLocal;
case MODULE_SCOPE:
DCHECK_IMPLIES(NeedsAndHasContext(), context_->IsModuleContext());
return ScopeTypeModule;
case SCRIPT_SCOPE:
DCHECK_IMPLIES(NeedsAndHasContext(), context_->IsScriptContext() ||
context_->IsNativeContext());
return ScopeTypeScript;
case WITH_SCOPE:
DCHECK_IMPLIES(NeedsAndHasContext(), context_->IsWithContext());
return ScopeTypeWith;
case CATCH_SCOPE:
DCHECK(context_->IsCatchContext());
return ScopeTypeCatch;
case BLOCK_SCOPE:
case CLASS_SCOPE:
DCHECK_IMPLIES(NeedsAndHasContext(), context_->IsBlockContext());
return ScopeTypeBlock;
case EVAL_SCOPE:
DCHECK_IMPLIES(NeedsAndHasContext(), 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() ||
context_->IsDebugEvaluateContext()) {
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());
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,
ScopeType scope_type) {
if (value->IsTheHole(isolate_)) {
// Reflect variables under TDZ as undefined in scope object.
if (scope_type == ScopeTypeScript &&
JSReceiver::HasOwnProperty(scope, name).FromMaybe(true)) {
// We also use the hole to represent overridden let-declarations via
// REPL mode in a script context. Catch this case.
return false;
}
value = isolate_->factory()->undefined_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, Type());
case ScopeTypeModule:
if (InInnerScope()) {
return VisitLocalScope(visitor, mode, Type());
}
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 (!NeedsAndHasContext()) 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;
}
bool ScopeIterator::ClosureScopeHasThisReference() const {
return !closure_scope_->has_this_declaration() &&
closure_scope_->HasThisReference();
}
void ScopeIterator::CollectLocalsFromCurrentScope() {
DCHECK(locals_->IsStringSet());
for (Variable* var : *current_scope_->locals()) {
if (var->location() == VariableLocation::PARAMETER ||
var->location() == VariableLocation::LOCAL) {
locals_ = StringSet::Add(isolate_, locals_, var->name());
}
}
}
#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 (NeedsAndHasContext()) {
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());
SharedFunctionInfo::EnsureSourcePositionsAvailable(
isolate_, handle(generator_->function().shared(), isolate_));
return generator_->source_position();
}
}
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->synchronized_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, ScopeTypeScript))
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_, ScopeTypeModule))
return;
int count_index = scope_info->ModuleVariableCountIndex();
int module_variable_count = Smi::cast(scope_info->get(count_index)).value();
Handle<SourceTextModule> 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);
if (ScopeInfo::VariableIsSynthetic(raw_name)) continue;
name = handle(raw_name, isolate_);
}
Handle<Object> value =
SourceTextModule::LoadVariable(isolate_, module, index);
if (visitor(name, value, ScopeTypeModule)) return;
}
}
bool ScopeIterator::VisitContextLocals(const Visitor& visitor,
Handle<ScopeInfo> scope_info,
Handle<Context> context,
ScopeType scope_type) 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 = scope_info->ContextHeaderLength() + i;
Handle<Object> value(context->get(context_index), isolate_);
if (visitor(name, value, scope_type)) return true;
}
return false;
}
bool ScopeIterator::VisitLocals(const Visitor& visitor, Mode mode,
ScopeType scope_type) const {
if (mode == Mode::STACK && current_scope_->is_declaration_scope() &&
current_scope_->AsDeclarationScope()->has_this_declaration()) {
// TODO(bmeurer): We should refactor the general variable lookup
// around "this", since the current way is rather hacky when the
// receiver is context-allocated.
auto this_var = current_scope_->AsDeclarationScope()->receiver();
Handle<Object> receiver =
this_var->location() == VariableLocation::CONTEXT
? handle(context_->get(this_var->index()), isolate_)
: frame_inspector_ == nullptr
? handle(generator_->receiver(), isolate_)
: frame_inspector_->GetReceiver();
if (receiver->IsOptimizedOut(isolate_)) {
receiver = isolate_->factory()->undefined_value();
}
if (visitor(isolate_->factory()->this_string(), receiver, scope_type))
return true;
}
if (current_scope_->is_function_scope()) {
Variable* function_var =
current_scope_->AsDeclarationScope()->function_var();
if (function_var != nullptr) {
Handle<JSFunction> function = frame_inspector_ == nullptr
? function_
: frame_inspector_->GetFunction();
Handle<String> name = function_var->name();
if (visitor(name, function, scope_type)) return true;
}
}
for (Variable* var : *current_scope_->locals()) {
DCHECK(!var->is_this());
if (ScopeInfo::VariableIsSynthetic(*var->name())) continue;
int index = var->index();
Handle<Object> value;
switch (var->location()) {
case VariableLocation::LOOKUP:
UNREACHABLE();
break;
case VariableLocation::REPL_GLOBAL:
// REPL declared variables are ignored for now.
case VariableLocation::UNALLOCATED:
continue;
case VariableLocation::PARAMETER: {
if (frame_inspector_ == nullptr) {
// Get the variable from the suspended generator.
DCHECK(!generator_.is_null());
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 = frame_inspector_->GetParameter(index);
if (value->IsOptimizedOut(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_);
} 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();
}
}
break;
case VariableLocation::CONTEXT:
if (mode == Mode::STACK) continue;
DCHECK(var->IsContextSlot());
value = handle(context_->get(index), isolate_);
break;
case VariableLocation::MODULE: {
if (mode == Mode::STACK) continue;
// if (var->IsExport()) continue;
Handle<SourceTextModule> module(context_->module(), isolate_);
value = SourceTextModule::LoadVariable(isolate_, module, var->index());
break;
}
}
if (visitor(var->name(), value, scope_type)) 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,
ScopeType scope_type) const {
if (InInnerScope()) {
if (VisitLocals(visitor, mode, scope_type)) 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() &&
!closure_scope_->HasThisReference()) {
if (visitor(isolate_->factory()->this_string(),
isolate_->factory()->undefined_value(), scope_type))
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,
scope_type))
return;
}
}
} else {
DCHECK_EQ(Mode::ALL, mode);
Handle<ScopeInfo> scope_info(context_->scope_info(), isolate_);
if (VisitContextLocals(visitor, scope_info, context_, scope_type)) return;
}
if (mode == Mode::ALL && HasContext()) {
DCHECK(!context_->IsScriptContext());
DCHECK(!context_->IsNativeContext());
DCHECK(!context_->IsWithContext());
if (!context_->scope_info().SloppyEvalCanExtendVars()) return;
if (context_->extension_object().is_null()) 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, scope_type)) 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::REPL_GLOBAL:
// Assignments to REPL declared variables are ignored for now.
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<SourceTextModule> module(context_->module(), isolate_);
SourceTextModule::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::HasProperty(&it);
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) {
DisallowHeapAllocation no_gc;
VariableMode mode;
InitializationFlag flag;
MaybeAssignedFlag maybe_assigned_flag;
IsStaticFlag is_static_flag;
int slot_index =
ScopeInfo::ContextSlotIndex(context_->scope_info(), *variable_name, &mode,
&flag, &maybe_assigned_flag, &is_static_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) {
DisallowHeapAllocation no_gc;
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 (SourceTextModuleDescriptor::GetCellIndexKind(cell_index) !=
SourceTextModuleDescriptor::kExport) {
return false;
}
Handle<SourceTextModule> module(context_->module(), isolate_);
SourceTextModule::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